/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2008-11-21 02:52:32 UTC
  • mto: This revision was merged to the branch mainline in revision 3844.
  • Revision ID: mbp@sourcefrog.net-20081121025232-dvsfoc4cuookgckz
Deprecated LockableFiles._escape

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
from bzrlib.lazy_import import lazy_import
 
18
lazy_import(globals(), """
 
19
import cStringIO
 
20
import re
 
21
import time
 
22
 
 
23
from bzrlib import (
 
24
    bzrdir,
 
25
    check,
 
26
    debug,
 
27
    errors,
 
28
    generate_ids,
 
29
    gpg,
 
30
    graph,
 
31
    lazy_regex,
 
32
    lockable_files,
 
33
    lockdir,
 
34
    lru_cache,
 
35
    osutils,
 
36
    remote,
 
37
    revision as _mod_revision,
 
38
    symbol_versioning,
 
39
    tsort,
 
40
    ui,
 
41
    )
 
42
from bzrlib.bundle import serializer
 
43
from bzrlib.revisiontree import RevisionTree
 
44
from bzrlib.store.versioned import VersionedFileStore
 
45
from bzrlib.testament import Testament
 
46
""")
 
47
 
 
48
from bzrlib import registry
 
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
50
from bzrlib.inter import InterObject
 
51
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
52
from bzrlib.symbol_versioning import (
 
53
        deprecated_method,
 
54
        one_one,
 
55
        one_two,
 
56
        one_six,
 
57
        )
 
58
from bzrlib.trace import (
 
59
    log_exception_quietly, note, mutter, mutter_callsite, warning)
 
60
 
 
61
 
 
62
# Old formats display a warning, but only once
 
63
_deprecation_warning_done = False
 
64
 
 
65
 
 
66
class CommitBuilder(object):
 
67
    """Provides an interface to build up a commit.
 
68
 
 
69
    This allows describing a tree to be committed without needing to 
 
70
    know the internals of the format of the repository.
 
71
    """
 
72
    
 
73
    # all clients should supply tree roots.
 
74
    record_root_entry = True
 
75
    # the default CommitBuilder does not manage trees whose root is versioned.
 
76
    _versioned_root = False
 
77
 
 
78
    def __init__(self, repository, parents, config, timestamp=None,
 
79
                 timezone=None, committer=None, revprops=None,
 
80
                 revision_id=None):
 
81
        """Initiate a CommitBuilder.
 
82
 
 
83
        :param repository: Repository to commit to.
 
84
        :param parents: Revision ids of the parents of the new revision.
 
85
        :param config: Configuration to use.
 
86
        :param timestamp: Optional timestamp recorded for commit.
 
87
        :param timezone: Optional timezone for timestamp.
 
88
        :param committer: Optional committer to set for commit.
 
89
        :param revprops: Optional dictionary of revision properties.
 
90
        :param revision_id: Optional revision id.
 
91
        """
 
92
        self._config = config
 
93
 
 
94
        if committer is None:
 
95
            self._committer = self._config.username()
 
96
        else:
 
97
            self._committer = committer
 
98
 
 
99
        self.new_inventory = Inventory(None)
 
100
        self._new_revision_id = revision_id
 
101
        self.parents = parents
 
102
        self.repository = repository
 
103
 
 
104
        self._revprops = {}
 
105
        if revprops is not None:
 
106
            self._revprops.update(revprops)
 
107
 
 
108
        if timestamp is None:
 
109
            timestamp = time.time()
 
110
        # Restrict resolution to 1ms
 
111
        self._timestamp = round(timestamp, 3)
 
112
 
 
113
        if timezone is None:
 
114
            self._timezone = osutils.local_time_offset()
 
115
        else:
 
116
            self._timezone = int(timezone)
 
117
 
 
118
        self._generate_revision_if_needed()
 
119
        self.__heads = graph.HeadsCache(repository.get_graph()).heads
 
120
 
 
121
    def commit(self, message):
 
122
        """Make the actual commit.
 
123
 
 
124
        :return: The revision id of the recorded revision.
 
125
        """
 
126
        rev = _mod_revision.Revision(
 
127
                       timestamp=self._timestamp,
 
128
                       timezone=self._timezone,
 
129
                       committer=self._committer,
 
130
                       message=message,
 
131
                       inventory_sha1=self.inv_sha1,
 
132
                       revision_id=self._new_revision_id,
 
133
                       properties=self._revprops)
 
134
        rev.parent_ids = self.parents
 
135
        self.repository.add_revision(self._new_revision_id, rev,
 
136
            self.new_inventory, self._config)
 
137
        self.repository.commit_write_group()
 
138
        return self._new_revision_id
 
139
 
 
140
    def abort(self):
 
141
        """Abort the commit that is being built.
 
142
        """
 
143
        self.repository.abort_write_group()
 
144
 
 
145
    def revision_tree(self):
 
146
        """Return the tree that was just committed.
 
147
 
 
148
        After calling commit() this can be called to get a RevisionTree
 
149
        representing the newly committed tree. This is preferred to
 
150
        calling Repository.revision_tree() because that may require
 
151
        deserializing the inventory, while we already have a copy in
 
152
        memory.
 
153
        """
 
154
        return RevisionTree(self.repository, self.new_inventory,
 
155
                            self._new_revision_id)
 
156
 
 
157
    def finish_inventory(self):
 
158
        """Tell the builder that the inventory is finished."""
 
159
        if self.new_inventory.root is None:
 
160
            raise AssertionError('Root entry should be supplied to'
 
161
                ' record_entry_contents, as of bzr 0.10.')
 
162
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
163
        self.new_inventory.revision_id = self._new_revision_id
 
164
        self.inv_sha1 = self.repository.add_inventory(
 
165
            self._new_revision_id,
 
166
            self.new_inventory,
 
167
            self.parents
 
168
            )
 
169
 
 
170
    def _gen_revision_id(self):
 
171
        """Return new revision-id."""
 
172
        return generate_ids.gen_revision_id(self._config.username(),
 
173
                                            self._timestamp)
 
174
 
 
175
    def _generate_revision_if_needed(self):
 
176
        """Create a revision id if None was supplied.
 
177
        
 
178
        If the repository can not support user-specified revision ids
 
179
        they should override this function and raise CannotSetRevisionId
 
180
        if _new_revision_id is not None.
 
181
 
 
182
        :raises: CannotSetRevisionId
 
183
        """
 
184
        if self._new_revision_id is None:
 
185
            self._new_revision_id = self._gen_revision_id()
 
186
            self.random_revid = True
 
187
        else:
 
188
            self.random_revid = False
 
189
 
 
190
    def _heads(self, file_id, revision_ids):
 
191
        """Calculate the graph heads for revision_ids in the graph of file_id.
 
192
 
 
193
        This can use either a per-file graph or a global revision graph as we
 
194
        have an identity relationship between the two graphs.
 
195
        """
 
196
        return self.__heads(revision_ids)
 
197
 
 
198
    def _check_root(self, ie, parent_invs, tree):
 
199
        """Helper for record_entry_contents.
 
200
 
 
201
        :param ie: An entry being added.
 
202
        :param parent_invs: The inventories of the parent revisions of the
 
203
            commit.
 
204
        :param tree: The tree that is being committed.
 
205
        """
 
206
        # In this revision format, root entries have no knit or weave When
 
207
        # serializing out to disk and back in root.revision is always
 
208
        # _new_revision_id
 
209
        ie.revision = self._new_revision_id
 
210
 
 
211
    def _get_delta(self, ie, basis_inv, path):
 
212
        """Get a delta against the basis inventory for ie."""
 
213
        if ie.file_id not in basis_inv:
 
214
            # add
 
215
            return (None, path, ie.file_id, ie)
 
216
        elif ie != basis_inv[ie.file_id]:
 
217
            # common but altered
 
218
            # TODO: avoid tis id2path call.
 
219
            return (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
 
220
        else:
 
221
            # common, unaltered
 
222
            return None
 
223
 
 
224
    def record_entry_contents(self, ie, parent_invs, path, tree,
 
225
        content_summary):
 
226
        """Record the content of ie from tree into the commit if needed.
 
227
 
 
228
        Side effect: sets ie.revision when unchanged
 
229
 
 
230
        :param ie: An inventory entry present in the commit.
 
231
        :param parent_invs: The inventories of the parent revisions of the
 
232
            commit.
 
233
        :param path: The path the entry is at in the tree.
 
234
        :param tree: The tree which contains this entry and should be used to 
 
235
            obtain content.
 
236
        :param content_summary: Summary data from the tree about the paths
 
237
            content - stat, length, exec, sha/link target. This is only
 
238
            accessed when the entry has a revision of None - that is when it is
 
239
            a candidate to commit.
 
240
        :return: A tuple (change_delta, version_recorded, fs_hash).
 
241
            change_delta is an inventory_delta change for this entry against
 
242
            the basis tree of the commit, or None if no change occured against
 
243
            the basis tree.
 
244
            version_recorded is True if a new version of the entry has been
 
245
            recorded. For instance, committing a merge where a file was only
 
246
            changed on the other side will return (delta, False).
 
247
            fs_hash is either None, or the hash details for the path (currently
 
248
            a tuple of the contents sha1 and the statvalue returned by
 
249
            tree.get_file_with_stat()).
 
250
        """
 
251
        if self.new_inventory.root is None:
 
252
            if ie.parent_id is not None:
 
253
                raise errors.RootMissing()
 
254
            self._check_root(ie, parent_invs, tree)
 
255
        if ie.revision is None:
 
256
            kind = content_summary[0]
 
257
        else:
 
258
            # ie is carried over from a prior commit
 
259
            kind = ie.kind
 
260
        # XXX: repository specific check for nested tree support goes here - if
 
261
        # the repo doesn't want nested trees we skip it ?
 
262
        if (kind == 'tree-reference' and
 
263
            not self.repository._format.supports_tree_reference):
 
264
            # mismatch between commit builder logic and repository:
 
265
            # this needs the entry creation pushed down into the builder.
 
266
            raise NotImplementedError('Missing repository subtree support.')
 
267
        self.new_inventory.add(ie)
 
268
 
 
269
        # TODO: slow, take it out of the inner loop.
 
270
        try:
 
271
            basis_inv = parent_invs[0]
 
272
        except IndexError:
 
273
            basis_inv = Inventory(root_id=None)
 
274
 
 
275
        # ie.revision is always None if the InventoryEntry is considered
 
276
        # for committing. We may record the previous parents revision if the
 
277
        # content is actually unchanged against a sole head.
 
278
        if ie.revision is not None:
 
279
            if not self._versioned_root and path == '':
 
280
                # repositories that do not version the root set the root's
 
281
                # revision to the new commit even when no change occurs, and
 
282
                # this masks when a change may have occurred against the basis,
 
283
                # so calculate if one happened.
 
284
                if ie.file_id in basis_inv:
 
285
                    delta = (basis_inv.id2path(ie.file_id), path,
 
286
                        ie.file_id, ie)
 
287
                else:
 
288
                    # add
 
289
                    delta = (None, path, ie.file_id, ie)
 
290
                return delta, False, None
 
291
            else:
 
292
                # we don't need to commit this, because the caller already
 
293
                # determined that an existing revision of this file is
 
294
                # appropriate. If its not being considered for committing then
 
295
                # it and all its parents to the root must be unaltered so
 
296
                # no-change against the basis.
 
297
                if ie.revision == self._new_revision_id:
 
298
                    raise AssertionError("Impossible situation, a skipped "
 
299
                        "inventory entry (%r) claims to be modified in this "
 
300
                        "commit (%r).", (ie, self._new_revision_id))
 
301
                return None, False, None
 
302
        # XXX: Friction: parent_candidates should return a list not a dict
 
303
        #      so that we don't have to walk the inventories again.
 
304
        parent_candiate_entries = ie.parent_candidates(parent_invs)
 
305
        head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
 
306
        heads = []
 
307
        for inv in parent_invs:
 
308
            if ie.file_id in inv:
 
309
                old_rev = inv[ie.file_id].revision
 
310
                if old_rev in head_set:
 
311
                    heads.append(inv[ie.file_id].revision)
 
312
                    head_set.remove(inv[ie.file_id].revision)
 
313
 
 
314
        store = False
 
315
        # now we check to see if we need to write a new record to the
 
316
        # file-graph.
 
317
        # We write a new entry unless there is one head to the ancestors, and
 
318
        # the kind-derived content is unchanged.
 
319
 
 
320
        # Cheapest check first: no ancestors, or more the one head in the
 
321
        # ancestors, we write a new node.
 
322
        if len(heads) != 1:
 
323
            store = True
 
324
        if not store:
 
325
            # There is a single head, look it up for comparison
 
326
            parent_entry = parent_candiate_entries[heads[0]]
 
327
            # if the non-content specific data has changed, we'll be writing a
 
328
            # node:
 
329
            if (parent_entry.parent_id != ie.parent_id or
 
330
                parent_entry.name != ie.name):
 
331
                store = True
 
332
        # now we need to do content specific checks:
 
333
        if not store:
 
334
            # if the kind changed the content obviously has
 
335
            if kind != parent_entry.kind:
 
336
                store = True
 
337
        # Stat cache fingerprint feedback for the caller - None as we usually
 
338
        # don't generate one.
 
339
        fingerprint = None
 
340
        if kind == 'file':
 
341
            if content_summary[2] is None:
 
342
                raise ValueError("Files must not have executable = None")
 
343
            if not store:
 
344
                if (# if the file length changed we have to store:
 
345
                    parent_entry.text_size != content_summary[1] or
 
346
                    # if the exec bit has changed we have to store:
 
347
                    parent_entry.executable != content_summary[2]):
 
348
                    store = True
 
349
                elif parent_entry.text_sha1 == content_summary[3]:
 
350
                    # all meta and content is unchanged (using a hash cache
 
351
                    # hit to check the sha)
 
352
                    ie.revision = parent_entry.revision
 
353
                    ie.text_size = parent_entry.text_size
 
354
                    ie.text_sha1 = parent_entry.text_sha1
 
355
                    ie.executable = parent_entry.executable
 
356
                    return self._get_delta(ie, basis_inv, path), False, None
 
357
                else:
 
358
                    # Either there is only a hash change(no hash cache entry,
 
359
                    # or same size content change), or there is no change on
 
360
                    # this file at all.
 
361
                    # Provide the parent's hash to the store layer, so that the
 
362
                    # content is unchanged we will not store a new node.
 
363
                    nostore_sha = parent_entry.text_sha1
 
364
            if store:
 
365
                # We want to record a new node regardless of the presence or
 
366
                # absence of a content change in the file.
 
367
                nostore_sha = None
 
368
            ie.executable = content_summary[2]
 
369
            file_obj, stat_value = tree.get_file_with_stat(ie.file_id, path)
 
370
            try:
 
371
                lines = file_obj.readlines()
 
372
            finally:
 
373
                file_obj.close()
 
374
            try:
 
375
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
 
376
                    ie.file_id, lines, heads, nostore_sha)
 
377
                # Let the caller know we generated a stat fingerprint.
 
378
                fingerprint = (ie.text_sha1, stat_value)
 
379
            except errors.ExistingContent:
 
380
                # Turns out that the file content was unchanged, and we were
 
381
                # only going to store a new node if it was changed. Carry over
 
382
                # the entry.
 
383
                ie.revision = parent_entry.revision
 
384
                ie.text_size = parent_entry.text_size
 
385
                ie.text_sha1 = parent_entry.text_sha1
 
386
                ie.executable = parent_entry.executable
 
387
                return self._get_delta(ie, basis_inv, path), False, None
 
388
        elif kind == 'directory':
 
389
            if not store:
 
390
                # all data is meta here, nothing specific to directory, so
 
391
                # carry over:
 
392
                ie.revision = parent_entry.revision
 
393
                return self._get_delta(ie, basis_inv, path), False, None
 
394
            lines = []
 
395
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
396
        elif kind == 'symlink':
 
397
            current_link_target = content_summary[3]
 
398
            if not store:
 
399
                # symlink target is not generic metadata, check if it has
 
400
                # changed.
 
401
                if current_link_target != parent_entry.symlink_target:
 
402
                    store = True
 
403
            if not store:
 
404
                # unchanged, carry over.
 
405
                ie.revision = parent_entry.revision
 
406
                ie.symlink_target = parent_entry.symlink_target
 
407
                return self._get_delta(ie, basis_inv, path), False, None
 
408
            ie.symlink_target = current_link_target
 
409
            lines = []
 
410
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
411
        elif kind == 'tree-reference':
 
412
            if not store:
 
413
                if content_summary[3] != parent_entry.reference_revision:
 
414
                    store = True
 
415
            if not store:
 
416
                # unchanged, carry over.
 
417
                ie.reference_revision = parent_entry.reference_revision
 
418
                ie.revision = parent_entry.revision
 
419
                return self._get_delta(ie, basis_inv, path), False, None
 
420
            ie.reference_revision = content_summary[3]
 
421
            lines = []
 
422
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
423
        else:
 
424
            raise NotImplementedError('unknown kind')
 
425
        ie.revision = self._new_revision_id
 
426
        return self._get_delta(ie, basis_inv, path), True, fingerprint
 
427
 
 
428
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
429
        # Note: as we read the content directly from the tree, we know its not
 
430
        # been turned into unicode or badly split - but a broken tree
 
431
        # implementation could give us bad output from readlines() so this is
 
432
        # not a guarantee of safety. What would be better is always checking
 
433
        # the content during test suite execution. RBC 20070912
 
434
        parent_keys = tuple((file_id, parent) for parent in parents)
 
435
        return self.repository.texts.add_lines(
 
436
            (file_id, self._new_revision_id), parent_keys, new_lines,
 
437
            nostore_sha=nostore_sha, random_id=self.random_revid,
 
438
            check_content=False)[0:2]
 
439
 
 
440
 
 
441
class RootCommitBuilder(CommitBuilder):
 
442
    """This commitbuilder actually records the root id"""
 
443
    
 
444
    # the root entry gets versioned properly by this builder.
 
445
    _versioned_root = True
 
446
 
 
447
    def _check_root(self, ie, parent_invs, tree):
 
448
        """Helper for record_entry_contents.
 
449
 
 
450
        :param ie: An entry being added.
 
451
        :param parent_invs: The inventories of the parent revisions of the
 
452
            commit.
 
453
        :param tree: The tree that is being committed.
 
454
        """
 
455
 
 
456
 
 
457
######################################################################
 
458
# Repositories
 
459
 
 
460
class Repository(object):
 
461
    """Repository holding history for one or more branches.
 
462
 
 
463
    The repository holds and retrieves historical information including
 
464
    revisions and file history.  It's normally accessed only by the Branch,
 
465
    which views a particular line of development through that history.
 
466
 
 
467
    The Repository builds on top of some byte storage facilies (the revisions,
 
468
    signatures, inventories and texts attributes) and a Transport, which
 
469
    respectively provide byte storage and a means to access the (possibly
 
470
    remote) disk.
 
471
 
 
472
    The byte storage facilities are addressed via tuples, which we refer to
 
473
    as 'keys' throughout the code base. Revision_keys, inventory_keys and
 
474
    signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
 
475
    (file_id, revision_id). We use this interface because it allows low
 
476
    friction with the underlying code that implements disk indices, network
 
477
    encoding and other parts of bzrlib.
 
478
 
 
479
    :ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
 
480
        the serialised revisions for the repository. This can be used to obtain
 
481
        revision graph information or to access raw serialised revisions.
 
482
        The result of trying to insert data into the repository via this store
 
483
        is undefined: it should be considered read-only except for implementors
 
484
        of repositories.
 
485
    :ivar signatures: A bzrlib.versionedfile.VersionedFiles instance containing
 
486
        the serialised signatures for the repository. This can be used to
 
487
        obtain access to raw serialised signatures.  The result of trying to
 
488
        insert data into the repository via this store is undefined: it should
 
489
        be considered read-only except for implementors of repositories.
 
490
    :ivar inventories: A bzrlib.versionedfile.VersionedFiles instance containing
 
491
        the serialised inventories for the repository. This can be used to
 
492
        obtain unserialised inventories.  The result of trying to insert data
 
493
        into the repository via this store is undefined: it should be
 
494
        considered read-only except for implementors of repositories.
 
495
    :ivar texts: A bzrlib.versionedfile.VersionedFiles instance containing the
 
496
        texts of files and directories for the repository. This can be used to
 
497
        obtain file texts or file graphs. Note that Repository.iter_file_bytes
 
498
        is usually a better interface for accessing file texts.
 
499
        The result of trying to insert data into the repository via this store
 
500
        is undefined: it should be considered read-only except for implementors
 
501
        of repositories.
 
502
    :ivar _transport: Transport for file access to repository, typically
 
503
        pointing to .bzr/repository.
 
504
    """
 
505
 
 
506
    # What class to use for a CommitBuilder. Often its simpler to change this
 
507
    # in a Repository class subclass rather than to override
 
508
    # get_commit_builder.
 
509
    _commit_builder_class = CommitBuilder
 
510
    # The search regex used by xml based repositories to determine what things
 
511
    # where changed in a single commit.
 
512
    _file_ids_altered_regex = lazy_regex.lazy_compile(
 
513
        r'file_id="(?P<file_id>[^"]+)"'
 
514
        r'.* revision="(?P<revision_id>[^"]+)"'
 
515
        )
 
516
 
 
517
    def abort_write_group(self, suppress_errors=False):
 
518
        """Commit the contents accrued within the current write group.
 
519
 
 
520
        :seealso: start_write_group.
 
521
        """
 
522
        if self._write_group is not self.get_transaction():
 
523
            # has an unlock or relock occured ?
 
524
            raise errors.BzrError('mismatched lock context and write group.')
 
525
        try:
 
526
            self._abort_write_group()
 
527
        except Exception, exc:
 
528
            self._write_group = None
 
529
            if not suppress_errors:
 
530
                raise
 
531
            mutter('abort_write_group failed')
 
532
            log_exception_quietly()
 
533
            note('bzr: ERROR (ignored): %s', exc)
 
534
        self._write_group = None
 
535
 
 
536
    def _abort_write_group(self):
 
537
        """Template method for per-repository write group cleanup.
 
538
        
 
539
        This is called during abort before the write group is considered to be 
 
540
        finished and should cleanup any internal state accrued during the write
 
541
        group. There is no requirement that data handed to the repository be
 
542
        *not* made available - this is not a rollback - but neither should any
 
543
        attempt be made to ensure that data added is fully commited. Abort is
 
544
        invoked when an error has occured so futher disk or network operations
 
545
        may not be possible or may error and if possible should not be
 
546
        attempted.
 
547
        """
 
548
 
 
549
    def add_fallback_repository(self, repository):
 
550
        """Add a repository to use for looking up data not held locally.
 
551
        
 
552
        :param repository: A repository.
 
553
        """
 
554
        if not self._format.supports_external_lookups:
 
555
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
 
556
        self._check_fallback_repository(repository)
 
557
        self._fallback_repositories.append(repository)
 
558
        self.texts.add_fallback_versioned_files(repository.texts)
 
559
        self.inventories.add_fallback_versioned_files(repository.inventories)
 
560
        self.revisions.add_fallback_versioned_files(repository.revisions)
 
561
        self.signatures.add_fallback_versioned_files(repository.signatures)
 
562
 
 
563
    def _check_fallback_repository(self, repository):
 
564
        """Check that this repository can fallback to repository safely.
 
565
 
 
566
        Raise an error if not.
 
567
        
 
568
        :param repository: A repository to fallback to.
 
569
        """
 
570
        return InterRepository._assert_same_model(self, repository)
 
571
 
 
572
    def add_inventory(self, revision_id, inv, parents):
 
573
        """Add the inventory inv to the repository as revision_id.
 
574
        
 
575
        :param parents: The revision ids of the parents that revision_id
 
576
                        is known to have and are in the repository already.
 
577
 
 
578
        :returns: The validator(which is a sha1 digest, though what is sha'd is
 
579
            repository format specific) of the serialized inventory.
 
580
        """
 
581
        if not self.is_in_write_group():
 
582
            raise AssertionError("%r not in write group" % (self,))
 
583
        _mod_revision.check_not_reserved_id(revision_id)
 
584
        if not (inv.revision_id is None or inv.revision_id == revision_id):
 
585
            raise AssertionError(
 
586
                "Mismatch between inventory revision"
 
587
                " id and insertion revid (%r, %r)"
 
588
                % (inv.revision_id, revision_id))
 
589
        if inv.root is None:
 
590
            raise AssertionError()
 
591
        inv_lines = self._serialise_inventory_to_lines(inv)
 
592
        return self._inventory_add_lines(revision_id, parents,
 
593
            inv_lines, check_content=False)
 
594
 
 
595
    def _inventory_add_lines(self, revision_id, parents, lines,
 
596
        check_content=True):
 
597
        """Store lines in inv_vf and return the sha1 of the inventory."""
 
598
        parents = [(parent,) for parent in parents]
 
599
        return self.inventories.add_lines((revision_id,), parents, lines,
 
600
            check_content=check_content)[0]
 
601
 
 
602
    def add_revision(self, revision_id, rev, inv=None, config=None):
 
603
        """Add rev to the revision store as revision_id.
 
604
 
 
605
        :param revision_id: the revision id to use.
 
606
        :param rev: The revision object.
 
607
        :param inv: The inventory for the revision. if None, it will be looked
 
608
                    up in the inventory storer
 
609
        :param config: If None no digital signature will be created.
 
610
                       If supplied its signature_needed method will be used
 
611
                       to determine if a signature should be made.
 
612
        """
 
613
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
 
614
        #       rev.parent_ids?
 
615
        _mod_revision.check_not_reserved_id(revision_id)
 
616
        if config is not None and config.signature_needed():
 
617
            if inv is None:
 
618
                inv = self.get_inventory(revision_id)
 
619
            plaintext = Testament(rev, inv).as_short_text()
 
620
            self.store_revision_signature(
 
621
                gpg.GPGStrategy(config), plaintext, revision_id)
 
622
        # check inventory present
 
623
        if not self.inventories.get_parent_map([(revision_id,)]):
 
624
            if inv is None:
 
625
                raise errors.WeaveRevisionNotPresent(revision_id,
 
626
                                                     self.inventories)
 
627
            else:
 
628
                # yes, this is not suitable for adding with ghosts.
 
629
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
 
630
                                                        rev.parent_ids)
 
631
        else:
 
632
            key = (revision_id,)
 
633
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
 
634
        self._add_revision(rev)
 
635
 
 
636
    def _add_revision(self, revision):
 
637
        text = self._serializer.write_revision_to_string(revision)
 
638
        key = (revision.revision_id,)
 
639
        parents = tuple((parent,) for parent in revision.parent_ids)
 
640
        self.revisions.add_lines(key, parents, osutils.split_lines(text))
 
641
 
 
642
    def all_revision_ids(self):
 
643
        """Returns a list of all the revision ids in the repository. 
 
644
 
 
645
        This is conceptually deprecated because code should generally work on
 
646
        the graph reachable from a particular revision, and ignore any other
 
647
        revisions that might be present.  There is no direct replacement
 
648
        method.
 
649
        """
 
650
        if 'evil' in debug.debug_flags:
 
651
            mutter_callsite(2, "all_revision_ids is linear with history.")
 
652
        return self._all_revision_ids()
 
653
 
 
654
    def _all_revision_ids(self):
 
655
        """Returns a list of all the revision ids in the repository. 
 
656
 
 
657
        These are in as much topological order as the underlying store can 
 
658
        present.
 
659
        """
 
660
        raise NotImplementedError(self._all_revision_ids)
 
661
 
 
662
    def break_lock(self):
 
663
        """Break a lock if one is present from another instance.
 
664
 
 
665
        Uses the ui factory to ask for confirmation if the lock may be from
 
666
        an active process.
 
667
        """
 
668
        self.control_files.break_lock()
 
669
 
 
670
    @needs_read_lock
 
671
    def _eliminate_revisions_not_present(self, revision_ids):
 
672
        """Check every revision id in revision_ids to see if we have it.
 
673
 
 
674
        Returns a set of the present revisions.
 
675
        """
 
676
        result = []
 
677
        graph = self.get_graph()
 
678
        parent_map = graph.get_parent_map(revision_ids)
 
679
        # The old API returned a list, should this actually be a set?
 
680
        return parent_map.keys()
 
681
 
 
682
    @staticmethod
 
683
    def create(a_bzrdir):
 
684
        """Construct the current default format repository in a_bzrdir."""
 
685
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
 
686
 
 
687
    def __init__(self, _format, a_bzrdir, control_files):
 
688
        """instantiate a Repository.
 
689
 
 
690
        :param _format: The format of the repository on disk.
 
691
        :param a_bzrdir: The BzrDir of the repository.
 
692
 
 
693
        In the future we will have a single api for all stores for
 
694
        getting file texts, inventories and revisions, then
 
695
        this construct will accept instances of those things.
 
696
        """
 
697
        super(Repository, self).__init__()
 
698
        self._format = _format
 
699
        # the following are part of the public API for Repository:
 
700
        self.bzrdir = a_bzrdir
 
701
        self.control_files = control_files
 
702
        self._transport = control_files._transport
 
703
        self.base = self._transport.base
 
704
        # for tests
 
705
        self._reconcile_does_inventory_gc = True
 
706
        self._reconcile_fixes_text_parents = False
 
707
        self._reconcile_backsup_inventory = True
 
708
        # not right yet - should be more semantically clear ? 
 
709
        # 
 
710
        # TODO: make sure to construct the right store classes, etc, depending
 
711
        # on whether escaping is required.
 
712
        self._warn_if_deprecated()
 
713
        self._write_group = None
 
714
        # Additional places to query for data.
 
715
        self._fallback_repositories = []
 
716
        # What order should fetch operations request streams in?
 
717
        # The default is unordered as that is the cheapest for an origin to
 
718
        # provide.
 
719
        self._fetch_order = 'unordered'
 
720
        # Does this repository use deltas that can be fetched as-deltas ?
 
721
        # (E.g. knits, where the knit deltas can be transplanted intact.
 
722
        # We default to False, which will ensure that enough data to get
 
723
        # a full text out of any fetch stream will be grabbed.
 
724
        self._fetch_uses_deltas = False
 
725
        # Should fetch trigger a reconcile after the fetch? Only needed for
 
726
        # some repository formats that can suffer internal inconsistencies.
 
727
        self._fetch_reconcile = False
 
728
 
 
729
    def __repr__(self):
 
730
        return '%s(%r)' % (self.__class__.__name__,
 
731
                           self.base)
 
732
 
 
733
    def has_same_location(self, other):
 
734
        """Returns a boolean indicating if this repository is at the same
 
735
        location as another repository.
 
736
 
 
737
        This might return False even when two repository objects are accessing
 
738
        the same physical repository via different URLs.
 
739
        """
 
740
        if self.__class__ is not other.__class__:
 
741
            return False
 
742
        return (self._transport.base == other._transport.base)
 
743
 
 
744
    def is_in_write_group(self):
 
745
        """Return True if there is an open write group.
 
746
 
 
747
        :seealso: start_write_group.
 
748
        """
 
749
        return self._write_group is not None
 
750
 
 
751
    def is_locked(self):
 
752
        return self.control_files.is_locked()
 
753
 
 
754
    def is_write_locked(self):
 
755
        """Return True if this object is write locked."""
 
756
        return self.is_locked() and self.control_files._lock_mode == 'w'
 
757
 
 
758
    def lock_write(self, token=None):
 
759
        """Lock this repository for writing.
 
760
 
 
761
        This causes caching within the repository obejct to start accumlating
 
762
        data during reads, and allows a 'write_group' to be obtained. Write
 
763
        groups must be used for actual data insertion.
 
764
        
 
765
        :param token: if this is already locked, then lock_write will fail
 
766
            unless the token matches the existing lock.
 
767
        :returns: a token if this instance supports tokens, otherwise None.
 
768
        :raises TokenLockingNotSupported: when a token is given but this
 
769
            instance doesn't support using token locks.
 
770
        :raises MismatchedToken: if the specified token doesn't match the token
 
771
            of the existing lock.
 
772
        :seealso: start_write_group.
 
773
 
 
774
        A token should be passed in if you know that you have locked the object
 
775
        some other way, and need to synchronise this object's state with that
 
776
        fact.
 
777
 
 
778
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
779
        """
 
780
        result = self.control_files.lock_write(token=token)
 
781
        for repo in self._fallback_repositories:
 
782
            # Writes don't affect fallback repos
 
783
            repo.lock_read()
 
784
        self._refresh_data()
 
785
        return result
 
786
 
 
787
    def lock_read(self):
 
788
        self.control_files.lock_read()
 
789
        for repo in self._fallback_repositories:
 
790
            repo.lock_read()
 
791
        self._refresh_data()
 
792
 
 
793
    def get_physical_lock_status(self):
 
794
        return self.control_files.get_physical_lock_status()
 
795
 
 
796
    def leave_lock_in_place(self):
 
797
        """Tell this repository not to release the physical lock when this
 
798
        object is unlocked.
 
799
        
 
800
        If lock_write doesn't return a token, then this method is not supported.
 
801
        """
 
802
        self.control_files.leave_in_place()
 
803
 
 
804
    def dont_leave_lock_in_place(self):
 
805
        """Tell this repository to release the physical lock when this
 
806
        object is unlocked, even if it didn't originally acquire it.
 
807
 
 
808
        If lock_write doesn't return a token, then this method is not supported.
 
809
        """
 
810
        self.control_files.dont_leave_in_place()
 
811
 
 
812
    @needs_read_lock
 
813
    def gather_stats(self, revid=None, committers=None):
 
814
        """Gather statistics from a revision id.
 
815
 
 
816
        :param revid: The revision id to gather statistics from, if None, then
 
817
            no revision specific statistics are gathered.
 
818
        :param committers: Optional parameter controlling whether to grab
 
819
            a count of committers from the revision specific statistics.
 
820
        :return: A dictionary of statistics. Currently this contains:
 
821
            committers: The number of committers if requested.
 
822
            firstrev: A tuple with timestamp, timezone for the penultimate left
 
823
                most ancestor of revid, if revid is not the NULL_REVISION.
 
824
            latestrev: A tuple with timestamp, timezone for revid, if revid is
 
825
                not the NULL_REVISION.
 
826
            revisions: The total revision count in the repository.
 
827
            size: An estimate disk size of the repository in bytes.
 
828
        """
 
829
        result = {}
 
830
        if revid and committers:
 
831
            result['committers'] = 0
 
832
        if revid and revid != _mod_revision.NULL_REVISION:
 
833
            if committers:
 
834
                all_committers = set()
 
835
            revisions = self.get_ancestry(revid)
 
836
            # pop the leading None
 
837
            revisions.pop(0)
 
838
            first_revision = None
 
839
            if not committers:
 
840
                # ignore the revisions in the middle - just grab first and last
 
841
                revisions = revisions[0], revisions[-1]
 
842
            for revision in self.get_revisions(revisions):
 
843
                if not first_revision:
 
844
                    first_revision = revision
 
845
                if committers:
 
846
                    all_committers.add(revision.committer)
 
847
            last_revision = revision
 
848
            if committers:
 
849
                result['committers'] = len(all_committers)
 
850
            result['firstrev'] = (first_revision.timestamp,
 
851
                first_revision.timezone)
 
852
            result['latestrev'] = (last_revision.timestamp,
 
853
                last_revision.timezone)
 
854
 
 
855
        # now gather global repository information
 
856
        # XXX: This is available for many repos regardless of listability.
 
857
        if self.bzrdir.root_transport.listable():
 
858
            # XXX: do we want to __define len__() ?
 
859
            # Maybe the versionedfiles object should provide a different
 
860
            # method to get the number of keys.
 
861
            result['revisions'] = len(self.revisions.keys())
 
862
            # result['size'] = t
 
863
        return result
 
864
 
 
865
    def find_branches(self, using=False):
 
866
        """Find branches underneath this repository.
 
867
 
 
868
        This will include branches inside other branches.
 
869
 
 
870
        :param using: If True, list only branches using this repository.
 
871
        """
 
872
        if using and not self.is_shared():
 
873
            try:
 
874
                return [self.bzrdir.open_branch()]
 
875
            except errors.NotBranchError:
 
876
                return []
 
877
        class Evaluator(object):
 
878
 
 
879
            def __init__(self):
 
880
                self.first_call = True
 
881
 
 
882
            def __call__(self, bzrdir):
 
883
                # On the first call, the parameter is always the bzrdir
 
884
                # containing the current repo.
 
885
                if not self.first_call:
 
886
                    try:
 
887
                        repository = bzrdir.open_repository()
 
888
                    except errors.NoRepositoryPresent:
 
889
                        pass
 
890
                    else:
 
891
                        return False, (None, repository)
 
892
                self.first_call = False
 
893
                try:
 
894
                    value = (bzrdir.open_branch(), None)
 
895
                except errors.NotBranchError:
 
896
                    value = (None, None)
 
897
                return True, value
 
898
 
 
899
        branches = []
 
900
        for branch, repository in bzrdir.BzrDir.find_bzrdirs(
 
901
                self.bzrdir.root_transport, evaluate=Evaluator()):
 
902
            if branch is not None:
 
903
                branches.append(branch)
 
904
            if not using and repository is not None:
 
905
                branches.extend(repository.find_branches())
 
906
        return branches
 
907
 
 
908
    @needs_read_lock
 
909
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
910
        """Return the revision ids that other has that this does not.
 
911
        
 
912
        These are returned in topological order.
 
913
 
 
914
        revision_id: only return revision ids included by revision_id.
 
915
        """
 
916
        return InterRepository.get(other, self).search_missing_revision_ids(
 
917
            revision_id, find_ghosts)
 
918
 
 
919
    @deprecated_method(one_two)
 
920
    @needs_read_lock
 
921
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
922
        """Return the revision ids that other has that this does not.
 
923
        
 
924
        These are returned in topological order.
 
925
 
 
926
        revision_id: only return revision ids included by revision_id.
 
927
        """
 
928
        keys =  self.search_missing_revision_ids(
 
929
            other, revision_id, find_ghosts).get_keys()
 
930
        other.lock_read()
 
931
        try:
 
932
            parents = other.get_graph().get_parent_map(keys)
 
933
        finally:
 
934
            other.unlock()
 
935
        return tsort.topo_sort(parents)
 
936
 
 
937
    @staticmethod
 
938
    def open(base):
 
939
        """Open the repository rooted at base.
 
940
 
 
941
        For instance, if the repository is at URL/.bzr/repository,
 
942
        Repository.open(URL) -> a Repository instance.
 
943
        """
 
944
        control = bzrdir.BzrDir.open(base)
 
945
        return control.open_repository()
 
946
 
 
947
    def copy_content_into(self, destination, revision_id=None):
 
948
        """Make a complete copy of the content in self into destination.
 
949
        
 
950
        This is a destructive operation! Do not use it on existing 
 
951
        repositories.
 
952
        """
 
953
        return InterRepository.get(self, destination).copy_content(revision_id)
 
954
 
 
955
    def commit_write_group(self):
 
956
        """Commit the contents accrued within the current write group.
 
957
 
 
958
        :seealso: start_write_group.
 
959
        """
 
960
        if self._write_group is not self.get_transaction():
 
961
            # has an unlock or relock occured ?
 
962
            raise errors.BzrError('mismatched lock context %r and '
 
963
                'write group %r.' %
 
964
                (self.get_transaction(), self._write_group))
 
965
        self._commit_write_group()
 
966
        self._write_group = None
 
967
 
 
968
    def _commit_write_group(self):
 
969
        """Template method for per-repository write group cleanup.
 
970
        
 
971
        This is called before the write group is considered to be 
 
972
        finished and should ensure that all data handed to the repository
 
973
        for writing during the write group is safely committed (to the 
 
974
        extent possible considering file system caching etc).
 
975
        """
 
976
 
 
977
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
 
978
        """Fetch the content required to construct revision_id from source.
 
979
 
 
980
        If revision_id is None all content is copied.
 
981
        :param find_ghosts: Find and copy revisions in the source that are
 
982
            ghosts in the target (and not reachable directly by walking out to
 
983
            the first-present revision in target from revision_id).
 
984
        """
 
985
        # fast path same-url fetch operations
 
986
        if self.has_same_location(source):
 
987
            # check that last_revision is in 'from' and then return a
 
988
            # no-operation.
 
989
            if (revision_id is not None and
 
990
                not _mod_revision.is_null(revision_id)):
 
991
                self.get_revision(revision_id)
 
992
            return 0, []
 
993
        # if there is no specific appropriate InterRepository, this will get
 
994
        # the InterRepository base class, which raises an
 
995
        # IncompatibleRepositories when asked to fetch.
 
996
        inter = InterRepository.get(source, self)
 
997
        return inter.fetch(revision_id=revision_id, pb=pb,
 
998
            find_ghosts=find_ghosts)
 
999
 
 
1000
    def create_bundle(self, target, base, fileobj, format=None):
 
1001
        return serializer.write_bundle(self, target, base, fileobj, format)
 
1002
 
 
1003
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
1004
                           timezone=None, committer=None, revprops=None,
 
1005
                           revision_id=None):
 
1006
        """Obtain a CommitBuilder for this repository.
 
1007
        
 
1008
        :param branch: Branch to commit to.
 
1009
        :param parents: Revision ids of the parents of the new revision.
 
1010
        :param config: Configuration to use.
 
1011
        :param timestamp: Optional timestamp recorded for commit.
 
1012
        :param timezone: Optional timezone for timestamp.
 
1013
        :param committer: Optional committer to set for commit.
 
1014
        :param revprops: Optional dictionary of revision properties.
 
1015
        :param revision_id: Optional revision id.
 
1016
        """
 
1017
        result = self._commit_builder_class(self, parents, config,
 
1018
            timestamp, timezone, committer, revprops, revision_id)
 
1019
        self.start_write_group()
 
1020
        return result
 
1021
 
 
1022
    def unlock(self):
 
1023
        if (self.control_files._lock_count == 1 and
 
1024
            self.control_files._lock_mode == 'w'):
 
1025
            if self._write_group is not None:
 
1026
                self.abort_write_group()
 
1027
                self.control_files.unlock()
 
1028
                raise errors.BzrError(
 
1029
                    'Must end write groups before releasing write locks.')
 
1030
        self.control_files.unlock()
 
1031
        for repo in self._fallback_repositories:
 
1032
            repo.unlock()
 
1033
 
 
1034
    @needs_read_lock
 
1035
    def clone(self, a_bzrdir, revision_id=None):
 
1036
        """Clone this repository into a_bzrdir using the current format.
 
1037
 
 
1038
        Currently no check is made that the format of this repository and
 
1039
        the bzrdir format are compatible. FIXME RBC 20060201.
 
1040
 
 
1041
        :return: The newly created destination repository.
 
1042
        """
 
1043
        # TODO: deprecate after 0.16; cloning this with all its settings is
 
1044
        # probably not very useful -- mbp 20070423
 
1045
        dest_repo = self._create_sprouting_repo(a_bzrdir, shared=self.is_shared())
 
1046
        self.copy_content_into(dest_repo, revision_id)
 
1047
        return dest_repo
 
1048
 
 
1049
    def start_write_group(self):
 
1050
        """Start a write group in the repository.
 
1051
 
 
1052
        Write groups are used by repositories which do not have a 1:1 mapping
 
1053
        between file ids and backend store to manage the insertion of data from
 
1054
        both fetch and commit operations.
 
1055
 
 
1056
        A write lock is required around the start_write_group/commit_write_group
 
1057
        for the support of lock-requiring repository formats.
 
1058
 
 
1059
        One can only insert data into a repository inside a write group.
 
1060
 
 
1061
        :return: None.
 
1062
        """
 
1063
        if not self.is_write_locked():
 
1064
            raise errors.NotWriteLocked(self)
 
1065
        if self._write_group:
 
1066
            raise errors.BzrError('already in a write group')
 
1067
        self._start_write_group()
 
1068
        # so we can detect unlock/relock - the write group is now entered.
 
1069
        self._write_group = self.get_transaction()
 
1070
 
 
1071
    def _start_write_group(self):
 
1072
        """Template method for per-repository write group startup.
 
1073
        
 
1074
        This is called before the write group is considered to be 
 
1075
        entered.
 
1076
        """
 
1077
 
 
1078
    @needs_read_lock
 
1079
    def sprout(self, to_bzrdir, revision_id=None):
 
1080
        """Create a descendent repository for new development.
 
1081
 
 
1082
        Unlike clone, this does not copy the settings of the repository.
 
1083
        """
 
1084
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
1085
        dest_repo.fetch(self, revision_id=revision_id)
 
1086
        return dest_repo
 
1087
 
 
1088
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
1089
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
1090
            # use target default format.
 
1091
            dest_repo = a_bzrdir.create_repository()
 
1092
        else:
 
1093
            # Most control formats need the repository to be specifically
 
1094
            # created, but on some old all-in-one formats it's not needed
 
1095
            try:
 
1096
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
 
1097
            except errors.UninitializableFormat:
 
1098
                dest_repo = a_bzrdir.open_repository()
 
1099
        return dest_repo
 
1100
 
 
1101
    @needs_read_lock
 
1102
    def has_revision(self, revision_id):
 
1103
        """True if this repository has a copy of the revision."""
 
1104
        return revision_id in self.has_revisions((revision_id,))
 
1105
 
 
1106
    @needs_read_lock
 
1107
    def has_revisions(self, revision_ids):
 
1108
        """Probe to find out the presence of multiple revisions.
 
1109
 
 
1110
        :param revision_ids: An iterable of revision_ids.
 
1111
        :return: A set of the revision_ids that were present.
 
1112
        """
 
1113
        parent_map = self.revisions.get_parent_map(
 
1114
            [(rev_id,) for rev_id in revision_ids])
 
1115
        result = set()
 
1116
        if _mod_revision.NULL_REVISION in revision_ids:
 
1117
            result.add(_mod_revision.NULL_REVISION)
 
1118
        result.update([key[0] for key in parent_map])
 
1119
        return result
 
1120
 
 
1121
    @needs_read_lock
 
1122
    def get_revision(self, revision_id):
 
1123
        """Return the Revision object for a named revision."""
 
1124
        return self.get_revisions([revision_id])[0]
 
1125
 
 
1126
    @needs_read_lock
 
1127
    def get_revision_reconcile(self, revision_id):
 
1128
        """'reconcile' helper routine that allows access to a revision always.
 
1129
        
 
1130
        This variant of get_revision does not cross check the weave graph
 
1131
        against the revision one as get_revision does: but it should only
 
1132
        be used by reconcile, or reconcile-alike commands that are correcting
 
1133
        or testing the revision graph.
 
1134
        """
 
1135
        return self._get_revisions([revision_id])[0]
 
1136
 
 
1137
    @needs_read_lock
 
1138
    def get_revisions(self, revision_ids):
 
1139
        """Get many revisions at once."""
 
1140
        return self._get_revisions(revision_ids)
 
1141
 
 
1142
    @needs_read_lock
 
1143
    def _get_revisions(self, revision_ids):
 
1144
        """Core work logic to get many revisions without sanity checks."""
 
1145
        for rev_id in revision_ids:
 
1146
            if not rev_id or not isinstance(rev_id, basestring):
 
1147
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
 
1148
        keys = [(key,) for key in revision_ids]
 
1149
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
 
1150
        revs = {}
 
1151
        for record in stream:
 
1152
            if record.storage_kind == 'absent':
 
1153
                raise errors.NoSuchRevision(self, record.key[0])
 
1154
            text = record.get_bytes_as('fulltext')
 
1155
            rev = self._serializer.read_revision_from_string(text)
 
1156
            revs[record.key[0]] = rev
 
1157
        return [revs[revid] for revid in revision_ids]
 
1158
 
 
1159
    @needs_read_lock
 
1160
    def get_revision_xml(self, revision_id):
 
1161
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
 
1162
        #       would have already do it.
 
1163
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
 
1164
        rev = self.get_revision(revision_id)
 
1165
        rev_tmp = cStringIO.StringIO()
 
1166
        # the current serializer..
 
1167
        self._serializer.write_revision(rev, rev_tmp)
 
1168
        rev_tmp.seek(0)
 
1169
        return rev_tmp.getvalue()
 
1170
 
 
1171
    def get_deltas_for_revisions(self, revisions):
 
1172
        """Produce a generator of revision deltas.
 
1173
        
 
1174
        Note that the input is a sequence of REVISIONS, not revision_ids.
 
1175
        Trees will be held in memory until the generator exits.
 
1176
        Each delta is relative to the revision's lefthand predecessor.
 
1177
        """
 
1178
        required_trees = set()
 
1179
        for revision in revisions:
 
1180
            required_trees.add(revision.revision_id)
 
1181
            required_trees.update(revision.parent_ids[:1])
 
1182
        trees = dict((t.get_revision_id(), t) for 
 
1183
                     t in self.revision_trees(required_trees))
 
1184
        for revision in revisions:
 
1185
            if not revision.parent_ids:
 
1186
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
 
1187
            else:
 
1188
                old_tree = trees[revision.parent_ids[0]]
 
1189
            yield trees[revision.revision_id].changes_from(old_tree)
 
1190
 
 
1191
    @needs_read_lock
 
1192
    def get_revision_delta(self, revision_id):
 
1193
        """Return the delta for one revision.
 
1194
 
 
1195
        The delta is relative to the left-hand predecessor of the
 
1196
        revision.
 
1197
        """
 
1198
        r = self.get_revision(revision_id)
 
1199
        return list(self.get_deltas_for_revisions([r]))[0]
 
1200
 
 
1201
    @needs_write_lock
 
1202
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1203
        signature = gpg_strategy.sign(plaintext)
 
1204
        self.add_signature_text(revision_id, signature)
 
1205
 
 
1206
    @needs_write_lock
 
1207
    def add_signature_text(self, revision_id, signature):
 
1208
        self.signatures.add_lines((revision_id,), (),
 
1209
            osutils.split_lines(signature))
 
1210
 
 
1211
    def find_text_key_references(self):
 
1212
        """Find the text key references within the repository.
 
1213
 
 
1214
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
1215
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1216
        altered it listed explicitly.
 
1217
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
1218
            to whether they were referred to by the inventory of the
 
1219
            revision_id that they contain. The inventory texts from all present
 
1220
            revision ids are assessed to generate this report.
 
1221
        """
 
1222
        revision_keys = self.revisions.keys()
 
1223
        w = self.inventories
 
1224
        pb = ui.ui_factory.nested_progress_bar()
 
1225
        try:
 
1226
            return self._find_text_key_references_from_xml_inventory_lines(
 
1227
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
 
1228
        finally:
 
1229
            pb.finished()
 
1230
 
 
1231
    def _find_text_key_references_from_xml_inventory_lines(self,
 
1232
        line_iterator):
 
1233
        """Core routine for extracting references to texts from inventories.
 
1234
 
 
1235
        This performs the translation of xml lines to revision ids.
 
1236
 
 
1237
        :param line_iterator: An iterator of lines, origin_version_id
 
1238
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
1239
            to whether they were referred to by the inventory of the
 
1240
            revision_id that they contain. Note that if that revision_id was
 
1241
            not part of the line_iterator's output then False will be given -
 
1242
            even though it may actually refer to that key.
 
1243
        """
 
1244
        if not self._serializer.support_altered_by_hack:
 
1245
            raise AssertionError(
 
1246
                "_find_text_key_references_from_xml_inventory_lines only "
 
1247
                "supported for branches which store inventory as unnested xml"
 
1248
                ", not on %r" % self)
 
1249
        result = {}
 
1250
 
 
1251
        # this code needs to read every new line in every inventory for the
 
1252
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
 
1253
        # not present in one of those inventories is unnecessary but not 
 
1254
        # harmful because we are filtering by the revision id marker in the
 
1255
        # inventory lines : we only select file ids altered in one of those  
 
1256
        # revisions. We don't need to see all lines in the inventory because
 
1257
        # only those added in an inventory in rev X can contain a revision=X
 
1258
        # line.
 
1259
        unescape_revid_cache = {}
 
1260
        unescape_fileid_cache = {}
 
1261
 
 
1262
        # jam 20061218 In a big fetch, this handles hundreds of thousands
 
1263
        # of lines, so it has had a lot of inlining and optimizing done.
 
1264
        # Sorry that it is a little bit messy.
 
1265
        # Move several functions to be local variables, since this is a long
 
1266
        # running loop.
 
1267
        search = self._file_ids_altered_regex.search
 
1268
        unescape = _unescape_xml
 
1269
        setdefault = result.setdefault
 
1270
        for line, line_key in line_iterator:
 
1271
            match = search(line)
 
1272
            if match is None:
 
1273
                continue
 
1274
            # One call to match.group() returning multiple items is quite a
 
1275
            # bit faster than 2 calls to match.group() each returning 1
 
1276
            file_id, revision_id = match.group('file_id', 'revision_id')
 
1277
 
 
1278
            # Inlining the cache lookups helps a lot when you make 170,000
 
1279
            # lines and 350k ids, versus 8.4 unique ids.
 
1280
            # Using a cache helps in 2 ways:
 
1281
            #   1) Avoids unnecessary decoding calls
 
1282
            #   2) Re-uses cached strings, which helps in future set and
 
1283
            #      equality checks.
 
1284
            # (2) is enough that removing encoding entirely along with
 
1285
            # the cache (so we are using plain strings) results in no
 
1286
            # performance improvement.
 
1287
            try:
 
1288
                revision_id = unescape_revid_cache[revision_id]
 
1289
            except KeyError:
 
1290
                unescaped = unescape(revision_id)
 
1291
                unescape_revid_cache[revision_id] = unescaped
 
1292
                revision_id = unescaped
 
1293
 
 
1294
            # Note that unconditionally unescaping means that we deserialise
 
1295
            # every fileid, which for general 'pull' is not great, but we don't
 
1296
            # really want to have some many fulltexts that this matters anyway.
 
1297
            # RBC 20071114.
 
1298
            try:
 
1299
                file_id = unescape_fileid_cache[file_id]
 
1300
            except KeyError:
 
1301
                unescaped = unescape(file_id)
 
1302
                unescape_fileid_cache[file_id] = unescaped
 
1303
                file_id = unescaped
 
1304
 
 
1305
            key = (file_id, revision_id)
 
1306
            setdefault(key, False)
 
1307
            if revision_id == line_key[-1]:
 
1308
                result[key] = True
 
1309
        return result
 
1310
 
 
1311
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
 
1312
        revision_ids):
 
1313
        """Helper routine for fileids_altered_by_revision_ids.
 
1314
 
 
1315
        This performs the translation of xml lines to revision ids.
 
1316
 
 
1317
        :param line_iterator: An iterator of lines, origin_version_id
 
1318
        :param revision_ids: The revision ids to filter for. This should be a
 
1319
            set or other type which supports efficient __contains__ lookups, as
 
1320
            the revision id from each parsed line will be looked up in the
 
1321
            revision_ids filter.
 
1322
        :return: a dictionary mapping altered file-ids to an iterable of
 
1323
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1324
        altered it listed explicitly.
 
1325
        """
 
1326
        result = {}
 
1327
        setdefault = result.setdefault
 
1328
        for key in \
 
1329
            self._find_text_key_references_from_xml_inventory_lines(
 
1330
                line_iterator).iterkeys():
 
1331
            # once data is all ensured-consistent; then this is
 
1332
            # if revision_id == version_id
 
1333
            if key[-1:] in revision_ids:
 
1334
                setdefault(key[0], set()).add(key[-1])
 
1335
        return result
 
1336
 
 
1337
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
 
1338
        """Find the file ids and versions affected by revisions.
 
1339
 
 
1340
        :param revisions: an iterable containing revision ids.
 
1341
        :param _inv_weave: The inventory weave from this repository or None.
 
1342
            If None, the inventory weave will be opened automatically.
 
1343
        :return: a dictionary mapping altered file-ids to an iterable of
 
1344
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1345
        altered it listed explicitly.
 
1346
        """
 
1347
        selected_keys = set((revid,) for revid in revision_ids)
 
1348
        w = _inv_weave or self.inventories
 
1349
        pb = ui.ui_factory.nested_progress_bar()
 
1350
        try:
 
1351
            return self._find_file_ids_from_xml_inventory_lines(
 
1352
                w.iter_lines_added_or_present_in_keys(
 
1353
                    selected_keys, pb=pb),
 
1354
                selected_keys)
 
1355
        finally:
 
1356
            pb.finished()
 
1357
 
 
1358
    def iter_files_bytes(self, desired_files):
 
1359
        """Iterate through file versions.
 
1360
 
 
1361
        Files will not necessarily be returned in the order they occur in
 
1362
        desired_files.  No specific order is guaranteed.
 
1363
 
 
1364
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
 
1365
        value supplied by the caller as part of desired_files.  It should
 
1366
        uniquely identify the file version in the caller's context.  (Examples:
 
1367
        an index number or a TreeTransform trans_id.)
 
1368
 
 
1369
        bytes_iterator is an iterable of bytestrings for the file.  The
 
1370
        kind of iterable and length of the bytestrings are unspecified, but for
 
1371
        this implementation, it is a list of bytes produced by
 
1372
        VersionedFile.get_record_stream().
 
1373
 
 
1374
        :param desired_files: a list of (file_id, revision_id, identifier)
 
1375
            triples
 
1376
        """
 
1377
        transaction = self.get_transaction()
 
1378
        text_keys = {}
 
1379
        for file_id, revision_id, callable_data in desired_files:
 
1380
            text_keys[(file_id, revision_id)] = callable_data
 
1381
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
 
1382
            if record.storage_kind == 'absent':
 
1383
                raise errors.RevisionNotPresent(record.key, self)
 
1384
            yield text_keys[record.key], record.get_bytes_as('fulltext')
 
1385
 
 
1386
    def _generate_text_key_index(self, text_key_references=None,
 
1387
        ancestors=None):
 
1388
        """Generate a new text key index for the repository.
 
1389
 
 
1390
        This is an expensive function that will take considerable time to run.
 
1391
 
 
1392
        :return: A dict mapping text keys ((file_id, revision_id) tuples) to a
 
1393
            list of parents, also text keys. When a given key has no parents,
 
1394
            the parents list will be [NULL_REVISION].
 
1395
        """
 
1396
        # All revisions, to find inventory parents.
 
1397
        if ancestors is None:
 
1398
            graph = self.get_graph()
 
1399
            ancestors = graph.get_parent_map(self.all_revision_ids())
 
1400
        if text_key_references is None:
 
1401
            text_key_references = self.find_text_key_references()
 
1402
        pb = ui.ui_factory.nested_progress_bar()
 
1403
        try:
 
1404
            return self._do_generate_text_key_index(ancestors,
 
1405
                text_key_references, pb)
 
1406
        finally:
 
1407
            pb.finished()
 
1408
 
 
1409
    def _do_generate_text_key_index(self, ancestors, text_key_references, pb):
 
1410
        """Helper for _generate_text_key_index to avoid deep nesting."""
 
1411
        revision_order = tsort.topo_sort(ancestors)
 
1412
        invalid_keys = set()
 
1413
        revision_keys = {}
 
1414
        for revision_id in revision_order:
 
1415
            revision_keys[revision_id] = set()
 
1416
        text_count = len(text_key_references)
 
1417
        # a cache of the text keys to allow reuse; costs a dict of all the
 
1418
        # keys, but saves a 2-tuple for every child of a given key.
 
1419
        text_key_cache = {}
 
1420
        for text_key, valid in text_key_references.iteritems():
 
1421
            if not valid:
 
1422
                invalid_keys.add(text_key)
 
1423
            else:
 
1424
                revision_keys[text_key[1]].add(text_key)
 
1425
            text_key_cache[text_key] = text_key
 
1426
        del text_key_references
 
1427
        text_index = {}
 
1428
        text_graph = graph.Graph(graph.DictParentsProvider(text_index))
 
1429
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1430
        # Set a cache with a size of 10 - this suffices for bzr.dev but may be
 
1431
        # too small for large or very branchy trees. However, for 55K path
 
1432
        # trees, it would be easy to use too much memory trivially. Ideally we
 
1433
        # could gauge this by looking at available real memory etc, but this is
 
1434
        # always a tricky proposition.
 
1435
        inventory_cache = lru_cache.LRUCache(10)
 
1436
        batch_size = 10 # should be ~150MB on a 55K path tree
 
1437
        batch_count = len(revision_order) / batch_size + 1
 
1438
        processed_texts = 0
 
1439
        pb.update("Calculating text parents.", processed_texts, text_count)
 
1440
        for offset in xrange(batch_count):
 
1441
            to_query = revision_order[offset * batch_size:(offset + 1) *
 
1442
                batch_size]
 
1443
            if not to_query:
 
1444
                break
 
1445
            for rev_tree in self.revision_trees(to_query):
 
1446
                revision_id = rev_tree.get_revision_id()
 
1447
                parent_ids = ancestors[revision_id]
 
1448
                for text_key in revision_keys[revision_id]:
 
1449
                    pb.update("Calculating text parents.", processed_texts)
 
1450
                    processed_texts += 1
 
1451
                    candidate_parents = []
 
1452
                    for parent_id in parent_ids:
 
1453
                        parent_text_key = (text_key[0], parent_id)
 
1454
                        try:
 
1455
                            check_parent = parent_text_key not in \
 
1456
                                revision_keys[parent_id]
 
1457
                        except KeyError:
 
1458
                            # the parent parent_id is a ghost:
 
1459
                            check_parent = False
 
1460
                            # truncate the derived graph against this ghost.
 
1461
                            parent_text_key = None
 
1462
                        if check_parent:
 
1463
                            # look at the parent commit details inventories to
 
1464
                            # determine possible candidates in the per file graph.
 
1465
                            # TODO: cache here.
 
1466
                            try:
 
1467
                                inv = inventory_cache[parent_id]
 
1468
                            except KeyError:
 
1469
                                inv = self.revision_tree(parent_id).inventory
 
1470
                                inventory_cache[parent_id] = inv
 
1471
                            parent_entry = inv._byid.get(text_key[0], None)
 
1472
                            if parent_entry is not None:
 
1473
                                parent_text_key = (
 
1474
                                    text_key[0], parent_entry.revision)
 
1475
                            else:
 
1476
                                parent_text_key = None
 
1477
                        if parent_text_key is not None:
 
1478
                            candidate_parents.append(
 
1479
                                text_key_cache[parent_text_key])
 
1480
                    parent_heads = text_graph.heads(candidate_parents)
 
1481
                    new_parents = list(parent_heads)
 
1482
                    new_parents.sort(key=lambda x:candidate_parents.index(x))
 
1483
                    if new_parents == []:
 
1484
                        new_parents = [NULL_REVISION]
 
1485
                    text_index[text_key] = new_parents
 
1486
 
 
1487
        for text_key in invalid_keys:
 
1488
            text_index[text_key] = [NULL_REVISION]
 
1489
        return text_index
 
1490
 
 
1491
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
1492
        """Get an iterable listing the keys of all the data introduced by a set
 
1493
        of revision IDs.
 
1494
 
 
1495
        The keys will be ordered so that the corresponding items can be safely
 
1496
        fetched and inserted in that order.
 
1497
 
 
1498
        :returns: An iterable producing tuples of (knit-kind, file-id,
 
1499
            versions).  knit-kind is one of 'file', 'inventory', 'signatures',
 
1500
            'revisions'.  file-id is None unless knit-kind is 'file'.
 
1501
        """
 
1502
        # XXX: it's a bit weird to control the inventory weave caching in this
 
1503
        # generator.  Ideally the caching would be done in fetch.py I think.  Or
 
1504
        # maybe this generator should explicitly have the contract that it
 
1505
        # should not be iterated until the previously yielded item has been
 
1506
        # processed?
 
1507
        inv_w = self.inventories
 
1508
 
 
1509
        # file ids that changed
 
1510
        file_ids = self.fileids_altered_by_revision_ids(revision_ids, inv_w)
 
1511
        count = 0
 
1512
        num_file_ids = len(file_ids)
 
1513
        for file_id, altered_versions in file_ids.iteritems():
 
1514
            if _files_pb is not None:
 
1515
                _files_pb.update("fetch texts", count, num_file_ids)
 
1516
            count += 1
 
1517
            yield ("file", file_id, altered_versions)
 
1518
        # We're done with the files_pb.  Note that it finished by the caller,
 
1519
        # just as it was created by the caller.
 
1520
        del _files_pb
 
1521
 
 
1522
        # inventory
 
1523
        yield ("inventory", None, revision_ids)
 
1524
 
 
1525
        # signatures
 
1526
        revisions_with_signatures = set()
 
1527
        for rev_id in revision_ids:
 
1528
            try:
 
1529
                self.get_signature_text(rev_id)
 
1530
            except errors.NoSuchRevision:
 
1531
                # not signed.
 
1532
                pass
 
1533
            else:
 
1534
                revisions_with_signatures.add(rev_id)
 
1535
        yield ("signatures", None, revisions_with_signatures)
 
1536
 
 
1537
        # revisions
 
1538
        yield ("revisions", None, revision_ids)
 
1539
 
 
1540
    @needs_read_lock
 
1541
    def get_inventory(self, revision_id):
 
1542
        """Get Inventory object by revision id."""
 
1543
        return self.iter_inventories([revision_id]).next()
 
1544
 
 
1545
    def iter_inventories(self, revision_ids):
 
1546
        """Get many inventories by revision_ids.
 
1547
 
 
1548
        This will buffer some or all of the texts used in constructing the
 
1549
        inventories in memory, but will only parse a single inventory at a
 
1550
        time.
 
1551
 
 
1552
        :return: An iterator of inventories.
 
1553
        """
 
1554
        if ((None in revision_ids)
 
1555
            or (_mod_revision.NULL_REVISION in revision_ids)):
 
1556
            raise ValueError('cannot get null revision inventory')
 
1557
        return self._iter_inventories(revision_ids)
 
1558
 
 
1559
    def _iter_inventories(self, revision_ids):
 
1560
        """single-document based inventory iteration."""
 
1561
        for text, revision_id in self._iter_inventory_xmls(revision_ids):
 
1562
            yield self.deserialise_inventory(revision_id, text)
 
1563
 
 
1564
    def _iter_inventory_xmls(self, revision_ids):
 
1565
        keys = [(revision_id,) for revision_id in revision_ids]
 
1566
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
1567
        texts = {}
 
1568
        for record in stream:
 
1569
            if record.storage_kind != 'absent':
 
1570
                texts[record.key] = record.get_bytes_as('fulltext')
 
1571
            else:
 
1572
                raise errors.NoSuchRevision(self, record.key)
 
1573
        for key in keys:
 
1574
            yield texts[key], key[-1]
 
1575
 
 
1576
    def deserialise_inventory(self, revision_id, xml):
 
1577
        """Transform the xml into an inventory object. 
 
1578
 
 
1579
        :param revision_id: The expected revision id of the inventory.
 
1580
        :param xml: A serialised inventory.
 
1581
        """
 
1582
        result = self._serializer.read_inventory_from_string(xml, revision_id)
 
1583
        if result.revision_id != revision_id:
 
1584
            raise AssertionError('revision id mismatch %s != %s' % (
 
1585
                result.revision_id, revision_id))
 
1586
        return result
 
1587
 
 
1588
    def serialise_inventory(self, inv):
 
1589
        return self._serializer.write_inventory_to_string(inv)
 
1590
 
 
1591
    def _serialise_inventory_to_lines(self, inv):
 
1592
        return self._serializer.write_inventory_to_lines(inv)
 
1593
 
 
1594
    def get_serializer_format(self):
 
1595
        return self._serializer.format_num
 
1596
 
 
1597
    @needs_read_lock
 
1598
    def get_inventory_xml(self, revision_id):
 
1599
        """Get inventory XML as a file object."""
 
1600
        texts = self._iter_inventory_xmls([revision_id])
 
1601
        try:
 
1602
            text, revision_id = texts.next()
 
1603
        except StopIteration:
 
1604
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
1605
        return text
 
1606
 
 
1607
    @needs_read_lock
 
1608
    def get_inventory_sha1(self, revision_id):
 
1609
        """Return the sha1 hash of the inventory entry
 
1610
        """
 
1611
        return self.get_revision(revision_id).inventory_sha1
 
1612
 
 
1613
    def iter_reverse_revision_history(self, revision_id):
 
1614
        """Iterate backwards through revision ids in the lefthand history
 
1615
 
 
1616
        :param revision_id: The revision id to start with.  All its lefthand
 
1617
            ancestors will be traversed.
 
1618
        """
 
1619
        graph = self.get_graph()
 
1620
        next_id = revision_id
 
1621
        while True:
 
1622
            if next_id in (None, _mod_revision.NULL_REVISION):
 
1623
                return
 
1624
            yield next_id
 
1625
            # Note: The following line may raise KeyError in the event of
 
1626
            # truncated history. We decided not to have a try:except:raise
 
1627
            # RevisionNotPresent here until we see a use for it, because of the
 
1628
            # cost in an inner loop that is by its very nature O(history).
 
1629
            # Robert Collins 20080326
 
1630
            parents = graph.get_parent_map([next_id])[next_id]
 
1631
            if len(parents) == 0:
 
1632
                return
 
1633
            else:
 
1634
                next_id = parents[0]
 
1635
 
 
1636
    @needs_read_lock
 
1637
    def get_revision_inventory(self, revision_id):
 
1638
        """Return inventory of a past revision."""
 
1639
        # TODO: Unify this with get_inventory()
 
1640
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
1641
        # must be the same as its revision, so this is trivial.
 
1642
        if revision_id is None:
 
1643
            # This does not make sense: if there is no revision,
 
1644
            # then it is the current tree inventory surely ?!
 
1645
            # and thus get_root_id() is something that looks at the last
 
1646
            # commit on the branch, and the get_root_id is an inventory check.
 
1647
            raise NotImplementedError
 
1648
            # return Inventory(self.get_root_id())
 
1649
        else:
 
1650
            return self.get_inventory(revision_id)
 
1651
 
 
1652
    def is_shared(self):
 
1653
        """Return True if this repository is flagged as a shared repository."""
 
1654
        raise NotImplementedError(self.is_shared)
 
1655
 
 
1656
    @needs_write_lock
 
1657
    def reconcile(self, other=None, thorough=False):
 
1658
        """Reconcile this repository."""
 
1659
        from bzrlib.reconcile import RepoReconciler
 
1660
        reconciler = RepoReconciler(self, thorough=thorough)
 
1661
        reconciler.reconcile()
 
1662
        return reconciler
 
1663
 
 
1664
    def _refresh_data(self):
 
1665
        """Helper called from lock_* to ensure coherency with disk.
 
1666
 
 
1667
        The default implementation does nothing; it is however possible
 
1668
        for repositories to maintain loaded indices across multiple locks
 
1669
        by checking inside their implementation of this method to see
 
1670
        whether their indices are still valid. This depends of course on
 
1671
        the disk format being validatable in this manner.
 
1672
        """
 
1673
 
 
1674
    @needs_read_lock
 
1675
    def revision_tree(self, revision_id):
 
1676
        """Return Tree for a revision on this branch.
 
1677
 
 
1678
        `revision_id` may be NULL_REVISION for the empty tree revision.
 
1679
        """
 
1680
        revision_id = _mod_revision.ensure_null(revision_id)
 
1681
        # TODO: refactor this to use an existing revision object
 
1682
        # so we don't need to read it in twice.
 
1683
        if revision_id == _mod_revision.NULL_REVISION:
 
1684
            return RevisionTree(self, Inventory(root_id=None), 
 
1685
                                _mod_revision.NULL_REVISION)
 
1686
        else:
 
1687
            inv = self.get_revision_inventory(revision_id)
 
1688
            return RevisionTree(self, inv, revision_id)
 
1689
 
 
1690
    def revision_trees(self, revision_ids):
 
1691
        """Return Tree for a revision on this branch.
 
1692
 
 
1693
        `revision_id` may not be None or 'null:'"""
 
1694
        inventories = self.iter_inventories(revision_ids)
 
1695
        for inv in inventories:
 
1696
            yield RevisionTree(self, inv, inv.revision_id)
 
1697
 
 
1698
    @needs_read_lock
 
1699
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1700
        """Return a list of revision-ids integrated by a revision.
 
1701
 
 
1702
        The first element of the list is always None, indicating the origin 
 
1703
        revision.  This might change when we have history horizons, or 
 
1704
        perhaps we should have a new API.
 
1705
        
 
1706
        This is topologically sorted.
 
1707
        """
 
1708
        if _mod_revision.is_null(revision_id):
 
1709
            return [None]
 
1710
        if not self.has_revision(revision_id):
 
1711
            raise errors.NoSuchRevision(self, revision_id)
 
1712
        graph = self.get_graph()
 
1713
        keys = set()
 
1714
        search = graph._make_breadth_first_searcher([revision_id])
 
1715
        while True:
 
1716
            try:
 
1717
                found, ghosts = search.next_with_ghosts()
 
1718
            except StopIteration:
 
1719
                break
 
1720
            keys.update(found)
 
1721
        if _mod_revision.NULL_REVISION in keys:
 
1722
            keys.remove(_mod_revision.NULL_REVISION)
 
1723
        if topo_sorted:
 
1724
            parent_map = graph.get_parent_map(keys)
 
1725
            keys = tsort.topo_sort(parent_map)
 
1726
        return [None] + list(keys)
 
1727
 
 
1728
    def pack(self):
 
1729
        """Compress the data within the repository.
 
1730
 
 
1731
        This operation only makes sense for some repository types. For other
 
1732
        types it should be a no-op that just returns.
 
1733
 
 
1734
        This stub method does not require a lock, but subclasses should use
 
1735
        @needs_write_lock as this is a long running call its reasonable to 
 
1736
        implicitly lock for the user.
 
1737
        """
 
1738
 
 
1739
    @needs_read_lock
 
1740
    @deprecated_method(one_six)
 
1741
    def print_file(self, file, revision_id):
 
1742
        """Print `file` to stdout.
 
1743
        
 
1744
        FIXME RBC 20060125 as John Meinel points out this is a bad api
 
1745
        - it writes to stdout, it assumes that that is valid etc. Fix
 
1746
        by creating a new more flexible convenience function.
 
1747
        """
 
1748
        tree = self.revision_tree(revision_id)
 
1749
        # use inventory as it was in that revision
 
1750
        file_id = tree.inventory.path2id(file)
 
1751
        if not file_id:
 
1752
            # TODO: jam 20060427 Write a test for this code path
 
1753
            #       it had a bug in it, and was raising the wrong
 
1754
            #       exception.
 
1755
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
 
1756
        tree.print_file(file_id)
 
1757
 
 
1758
    def get_transaction(self):
 
1759
        return self.control_files.get_transaction()
 
1760
 
 
1761
    @deprecated_method(one_one)
 
1762
    def get_parents(self, revision_ids):
 
1763
        """See StackedParentsProvider.get_parents"""
 
1764
        parent_map = self.get_parent_map(revision_ids)
 
1765
        return [parent_map.get(r, None) for r in revision_ids]
 
1766
 
 
1767
    def get_parent_map(self, revision_ids):
 
1768
        """See graph._StackedParentsProvider.get_parent_map"""
 
1769
        # revisions index works in keys; this just works in revisions
 
1770
        # therefore wrap and unwrap
 
1771
        query_keys = []
 
1772
        result = {}
 
1773
        for revision_id in revision_ids:
 
1774
            if revision_id == _mod_revision.NULL_REVISION:
 
1775
                result[revision_id] = ()
 
1776
            elif revision_id is None:
 
1777
                raise ValueError('get_parent_map(None) is not valid')
 
1778
            else:
 
1779
                query_keys.append((revision_id ,))
 
1780
        for ((revision_id,), parent_keys) in \
 
1781
                self.revisions.get_parent_map(query_keys).iteritems():
 
1782
            if parent_keys:
 
1783
                result[revision_id] = tuple(parent_revid
 
1784
                    for (parent_revid,) in parent_keys)
 
1785
            else:
 
1786
                result[revision_id] = (_mod_revision.NULL_REVISION,)
 
1787
        return result
 
1788
 
 
1789
    def _make_parents_provider(self):
 
1790
        return self
 
1791
 
 
1792
    def get_graph(self, other_repository=None):
 
1793
        """Return the graph walker for this repository format"""
 
1794
        parents_provider = self._make_parents_provider()
 
1795
        if (other_repository is not None and
 
1796
            not self.has_same_location(other_repository)):
 
1797
            parents_provider = graph._StackedParentsProvider(
 
1798
                [parents_provider, other_repository._make_parents_provider()])
 
1799
        return graph.Graph(parents_provider)
 
1800
 
 
1801
    def _get_versioned_file_checker(self):
 
1802
        """Return an object suitable for checking versioned files."""
 
1803
        return _VersionedFileChecker(self)
 
1804
 
 
1805
    def revision_ids_to_search_result(self, result_set):
 
1806
        """Convert a set of revision ids to a graph SearchResult."""
 
1807
        result_parents = set()
 
1808
        for parents in self.get_graph().get_parent_map(
 
1809
            result_set).itervalues():
 
1810
            result_parents.update(parents)
 
1811
        included_keys = result_set.intersection(result_parents)
 
1812
        start_keys = result_set.difference(included_keys)
 
1813
        exclude_keys = result_parents.difference(result_set)
 
1814
        result = graph.SearchResult(start_keys, exclude_keys,
 
1815
            len(result_set), result_set)
 
1816
        return result
 
1817
 
 
1818
    @needs_write_lock
 
1819
    def set_make_working_trees(self, new_value):
 
1820
        """Set the policy flag for making working trees when creating branches.
 
1821
 
 
1822
        This only applies to branches that use this repository.
 
1823
 
 
1824
        The default is 'True'.
 
1825
        :param new_value: True to restore the default, False to disable making
 
1826
                          working trees.
 
1827
        """
 
1828
        raise NotImplementedError(self.set_make_working_trees)
 
1829
    
 
1830
    def make_working_trees(self):
 
1831
        """Returns the policy for making working trees on new branches."""
 
1832
        raise NotImplementedError(self.make_working_trees)
 
1833
 
 
1834
    @needs_write_lock
 
1835
    def sign_revision(self, revision_id, gpg_strategy):
 
1836
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
1837
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1838
 
 
1839
    @needs_read_lock
 
1840
    def has_signature_for_revision_id(self, revision_id):
 
1841
        """Query for a revision signature for revision_id in the repository."""
 
1842
        if not self.has_revision(revision_id):
 
1843
            raise errors.NoSuchRevision(self, revision_id)
 
1844
        sig_present = (1 == len(
 
1845
            self.signatures.get_parent_map([(revision_id,)])))
 
1846
        return sig_present
 
1847
 
 
1848
    @needs_read_lock
 
1849
    def get_signature_text(self, revision_id):
 
1850
        """Return the text for a signature."""
 
1851
        stream = self.signatures.get_record_stream([(revision_id,)],
 
1852
            'unordered', True)
 
1853
        record = stream.next()
 
1854
        if record.storage_kind == 'absent':
 
1855
            raise errors.NoSuchRevision(self, revision_id)
 
1856
        return record.get_bytes_as('fulltext')
 
1857
 
 
1858
    @needs_read_lock
 
1859
    def check(self, revision_ids=None):
 
1860
        """Check consistency of all history of given revision_ids.
 
1861
 
 
1862
        Different repository implementations should override _check().
 
1863
 
 
1864
        :param revision_ids: A non-empty list of revision_ids whose ancestry
 
1865
             will be checked.  Typically the last revision_id of a branch.
 
1866
        """
 
1867
        return self._check(revision_ids)
 
1868
 
 
1869
    def _check(self, revision_ids):
 
1870
        result = check.Check(self)
 
1871
        result.check()
 
1872
        return result
 
1873
 
 
1874
    def _warn_if_deprecated(self):
 
1875
        global _deprecation_warning_done
 
1876
        if _deprecation_warning_done:
 
1877
            return
 
1878
        _deprecation_warning_done = True
 
1879
        warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
 
1880
                % (self._format, self.bzrdir.transport.base))
 
1881
 
 
1882
    def supports_rich_root(self):
 
1883
        return self._format.rich_root_data
 
1884
 
 
1885
    def _check_ascii_revisionid(self, revision_id, method):
 
1886
        """Private helper for ascii-only repositories."""
 
1887
        # weave repositories refuse to store revisionids that are non-ascii.
 
1888
        if revision_id is not None:
 
1889
            # weaves require ascii revision ids.
 
1890
            if isinstance(revision_id, unicode):
 
1891
                try:
 
1892
                    revision_id.encode('ascii')
 
1893
                except UnicodeEncodeError:
 
1894
                    raise errors.NonAsciiRevisionId(method, self)
 
1895
            else:
 
1896
                try:
 
1897
                    revision_id.decode('ascii')
 
1898
                except UnicodeDecodeError:
 
1899
                    raise errors.NonAsciiRevisionId(method, self)
 
1900
    
 
1901
    def revision_graph_can_have_wrong_parents(self):
 
1902
        """Is it possible for this repository to have a revision graph with
 
1903
        incorrect parents?
 
1904
 
 
1905
        If True, then this repository must also implement
 
1906
        _find_inconsistent_revision_parents so that check and reconcile can
 
1907
        check for inconsistencies before proceeding with other checks that may
 
1908
        depend on the revision index being consistent.
 
1909
        """
 
1910
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
 
1911
 
 
1912
 
 
1913
# remove these delegates a while after bzr 0.15
 
1914
def __make_delegated(name, from_module):
 
1915
    def _deprecated_repository_forwarder():
 
1916
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
 
1917
            % (name, from_module),
 
1918
            DeprecationWarning,
 
1919
            stacklevel=2)
 
1920
        m = __import__(from_module, globals(), locals(), [name])
 
1921
        try:
 
1922
            return getattr(m, name)
 
1923
        except AttributeError:
 
1924
            raise AttributeError('module %s has no name %s'
 
1925
                    % (m, name))
 
1926
    globals()[name] = _deprecated_repository_forwarder
 
1927
 
 
1928
for _name in [
 
1929
        'AllInOneRepository',
 
1930
        'WeaveMetaDirRepository',
 
1931
        'PreSplitOutRepositoryFormat',
 
1932
        'RepositoryFormat4',
 
1933
        'RepositoryFormat5',
 
1934
        'RepositoryFormat6',
 
1935
        'RepositoryFormat7',
 
1936
        ]:
 
1937
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
 
1938
 
 
1939
for _name in [
 
1940
        'KnitRepository',
 
1941
        'RepositoryFormatKnit',
 
1942
        'RepositoryFormatKnit1',
 
1943
        ]:
 
1944
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
 
1945
 
 
1946
 
 
1947
def install_revision(repository, rev, revision_tree):
 
1948
    """Install all revision data into a repository."""
 
1949
    install_revisions(repository, [(rev, revision_tree, None)])
 
1950
 
 
1951
 
 
1952
def install_revisions(repository, iterable, num_revisions=None, pb=None):
 
1953
    """Install all revision data into a repository.
 
1954
 
 
1955
    Accepts an iterable of revision, tree, signature tuples.  The signature
 
1956
    may be None.
 
1957
    """
 
1958
    repository.start_write_group()
 
1959
    try:
 
1960
        for n, (revision, revision_tree, signature) in enumerate(iterable):
 
1961
            _install_revision(repository, revision, revision_tree, signature)
 
1962
            if pb is not None:
 
1963
                pb.update('Transferring revisions', n + 1, num_revisions)
 
1964
    except:
 
1965
        repository.abort_write_group()
 
1966
        raise
 
1967
    else:
 
1968
        repository.commit_write_group()
 
1969
 
 
1970
 
 
1971
def _install_revision(repository, rev, revision_tree, signature):
 
1972
    """Install all revision data into a repository."""
 
1973
    present_parents = []
 
1974
    parent_trees = {}
 
1975
    for p_id in rev.parent_ids:
 
1976
        if repository.has_revision(p_id):
 
1977
            present_parents.append(p_id)
 
1978
            parent_trees[p_id] = repository.revision_tree(p_id)
 
1979
        else:
 
1980
            parent_trees[p_id] = repository.revision_tree(
 
1981
                                     _mod_revision.NULL_REVISION)
 
1982
 
 
1983
    inv = revision_tree.inventory
 
1984
    entries = inv.iter_entries()
 
1985
    # backwards compatibility hack: skip the root id.
 
1986
    if not repository.supports_rich_root():
 
1987
        path, root = entries.next()
 
1988
        if root.revision != rev.revision_id:
 
1989
            raise errors.IncompatibleRevision(repr(repository))
 
1990
    text_keys = {}
 
1991
    for path, ie in entries:
 
1992
        text_keys[(ie.file_id, ie.revision)] = ie
 
1993
    text_parent_map = repository.texts.get_parent_map(text_keys)
 
1994
    missing_texts = set(text_keys) - set(text_parent_map)
 
1995
    # Add the texts that are not already present
 
1996
    for text_key in missing_texts:
 
1997
        ie = text_keys[text_key]
 
1998
        text_parents = []
 
1999
        # FIXME: TODO: The following loop overlaps/duplicates that done by
 
2000
        # commit to determine parents. There is a latent/real bug here where
 
2001
        # the parents inserted are not those commit would do - in particular
 
2002
        # they are not filtered by heads(). RBC, AB
 
2003
        for revision, tree in parent_trees.iteritems():
 
2004
            if ie.file_id not in tree:
 
2005
                continue
 
2006
            parent_id = tree.inventory[ie.file_id].revision
 
2007
            if parent_id in text_parents:
 
2008
                continue
 
2009
            text_parents.append((ie.file_id, parent_id))
 
2010
        lines = revision_tree.get_file(ie.file_id).readlines()
 
2011
        repository.texts.add_lines(text_key, text_parents, lines)
 
2012
    try:
 
2013
        # install the inventory
 
2014
        repository.add_inventory(rev.revision_id, inv, present_parents)
 
2015
    except errors.RevisionAlreadyPresent:
 
2016
        pass
 
2017
    if signature is not None:
 
2018
        repository.add_signature_text(rev.revision_id, signature)
 
2019
    repository.add_revision(rev.revision_id, rev, inv)
 
2020
 
 
2021
 
 
2022
class MetaDirRepository(Repository):
 
2023
    """Repositories in the new meta-dir layout.
 
2024
    
 
2025
    :ivar _transport: Transport for access to repository control files,
 
2026
        typically pointing to .bzr/repository.
 
2027
    """
 
2028
 
 
2029
    def __init__(self, _format, a_bzrdir, control_files):
 
2030
        super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
 
2031
        self._transport = control_files._transport
 
2032
 
 
2033
    def is_shared(self):
 
2034
        """Return True if this repository is flagged as a shared repository."""
 
2035
        return self._transport.has('shared-storage')
 
2036
 
 
2037
    @needs_write_lock
 
2038
    def set_make_working_trees(self, new_value):
 
2039
        """Set the policy flag for making working trees when creating branches.
 
2040
 
 
2041
        This only applies to branches that use this repository.
 
2042
 
 
2043
        The default is 'True'.
 
2044
        :param new_value: True to restore the default, False to disable making
 
2045
                          working trees.
 
2046
        """
 
2047
        if new_value:
 
2048
            try:
 
2049
                self._transport.delete('no-working-trees')
 
2050
            except errors.NoSuchFile:
 
2051
                pass
 
2052
        else:
 
2053
            self._transport.put_bytes('no-working-trees', '',
 
2054
                mode=self.bzrdir._get_file_mode())
 
2055
    
 
2056
    def make_working_trees(self):
 
2057
        """Returns the policy for making working trees on new branches."""
 
2058
        return not self._transport.has('no-working-trees')
 
2059
 
 
2060
 
 
2061
class MetaDirVersionedFileRepository(MetaDirRepository):
 
2062
    """Repositories in a meta-dir, that work via versioned file objects."""
 
2063
 
 
2064
    def __init__(self, _format, a_bzrdir, control_files):
 
2065
        super(MetaDirVersionedFileRepository, self).__init__(_format, a_bzrdir,
 
2066
            control_files)
 
2067
 
 
2068
 
 
2069
class RepositoryFormatRegistry(registry.Registry):
 
2070
    """Registry of RepositoryFormats."""
 
2071
 
 
2072
    def get(self, format_string):
 
2073
        r = registry.Registry.get(self, format_string)
 
2074
        if callable(r):
 
2075
            r = r()
 
2076
        return r
 
2077
    
 
2078
 
 
2079
format_registry = RepositoryFormatRegistry()
 
2080
"""Registry of formats, indexed by their identifying format string.
 
2081
 
 
2082
This can contain either format instances themselves, or classes/factories that
 
2083
can be called to obtain one.
 
2084
"""
 
2085
 
 
2086
 
 
2087
#####################################################################
 
2088
# Repository Formats
 
2089
 
 
2090
class RepositoryFormat(object):
 
2091
    """A repository format.
 
2092
 
 
2093
    Formats provide three things:
 
2094
     * An initialization routine to construct repository data on disk.
 
2095
     * a format string which is used when the BzrDir supports versioned
 
2096
       children.
 
2097
     * an open routine which returns a Repository instance.
 
2098
 
 
2099
    There is one and only one Format subclass for each on-disk format. But
 
2100
    there can be one Repository subclass that is used for several different
 
2101
    formats. The _format attribute on a Repository instance can be used to
 
2102
    determine the disk format.
 
2103
 
 
2104
    Formats are placed in an dict by their format string for reference 
 
2105
    during opening. These should be subclasses of RepositoryFormat
 
2106
    for consistency.
 
2107
 
 
2108
    Once a format is deprecated, just deprecate the initialize and open
 
2109
    methods on the format class. Do not deprecate the object, as the 
 
2110
    object will be created every system load.
 
2111
 
 
2112
    Common instance attributes:
 
2113
    _matchingbzrdir - the bzrdir format that the repository format was
 
2114
    originally written to work with. This can be used if manually
 
2115
    constructing a bzrdir and repository, or more commonly for test suite
 
2116
    parameterization.
 
2117
    """
 
2118
 
 
2119
    # Set to True or False in derived classes. True indicates that the format
 
2120
    # supports ghosts gracefully.
 
2121
    supports_ghosts = None
 
2122
    # Can this repository be given external locations to lookup additional
 
2123
    # data. Set to True or False in derived classes.
 
2124
    supports_external_lookups = None
 
2125
 
 
2126
    def __str__(self):
 
2127
        return "<%s>" % self.__class__.__name__
 
2128
 
 
2129
    def __eq__(self, other):
 
2130
        # format objects are generally stateless
 
2131
        return isinstance(other, self.__class__)
 
2132
 
 
2133
    def __ne__(self, other):
 
2134
        return not self == other
 
2135
 
 
2136
    @classmethod
 
2137
    def find_format(klass, a_bzrdir):
 
2138
        """Return the format for the repository object in a_bzrdir.
 
2139
        
 
2140
        This is used by bzr native formats that have a "format" file in
 
2141
        the repository.  Other methods may be used by different types of 
 
2142
        control directory.
 
2143
        """
 
2144
        try:
 
2145
            transport = a_bzrdir.get_repository_transport(None)
 
2146
            format_string = transport.get("format").read()
 
2147
            return format_registry.get(format_string)
 
2148
        except errors.NoSuchFile:
 
2149
            raise errors.NoRepositoryPresent(a_bzrdir)
 
2150
        except KeyError:
 
2151
            raise errors.UnknownFormatError(format=format_string,
 
2152
                                            kind='repository')
 
2153
 
 
2154
    @classmethod
 
2155
    def register_format(klass, format):
 
2156
        format_registry.register(format.get_format_string(), format)
 
2157
 
 
2158
    @classmethod
 
2159
    def unregister_format(klass, format):
 
2160
        format_registry.remove(format.get_format_string())
 
2161
    
 
2162
    @classmethod
 
2163
    def get_default_format(klass):
 
2164
        """Return the current default format."""
 
2165
        from bzrlib import bzrdir
 
2166
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
2167
 
 
2168
    def get_format_string(self):
 
2169
        """Return the ASCII format string that identifies this format.
 
2170
        
 
2171
        Note that in pre format ?? repositories the format string is 
 
2172
        not permitted nor written to disk.
 
2173
        """
 
2174
        raise NotImplementedError(self.get_format_string)
 
2175
 
 
2176
    def get_format_description(self):
 
2177
        """Return the short description for this format."""
 
2178
        raise NotImplementedError(self.get_format_description)
 
2179
 
 
2180
    # TODO: this shouldn't be in the base class, it's specific to things that
 
2181
    # use weaves or knits -- mbp 20070207
 
2182
    def _get_versioned_file_store(self,
 
2183
                                  name,
 
2184
                                  transport,
 
2185
                                  control_files,
 
2186
                                  prefixed=True,
 
2187
                                  versionedfile_class=None,
 
2188
                                  versionedfile_kwargs={},
 
2189
                                  escaped=False):
 
2190
        if versionedfile_class is None:
 
2191
            versionedfile_class = self._versionedfile_class
 
2192
        weave_transport = control_files._transport.clone(name)
 
2193
        dir_mode = control_files._dir_mode
 
2194
        file_mode = control_files._file_mode
 
2195
        return VersionedFileStore(weave_transport, prefixed=prefixed,
 
2196
                                  dir_mode=dir_mode,
 
2197
                                  file_mode=file_mode,
 
2198
                                  versionedfile_class=versionedfile_class,
 
2199
                                  versionedfile_kwargs=versionedfile_kwargs,
 
2200
                                  escaped=escaped)
 
2201
 
 
2202
    def initialize(self, a_bzrdir, shared=False):
 
2203
        """Initialize a repository of this format in a_bzrdir.
 
2204
 
 
2205
        :param a_bzrdir: The bzrdir to put the new repository in it.
 
2206
        :param shared: The repository should be initialized as a sharable one.
 
2207
        :returns: The new repository object.
 
2208
        
 
2209
        This may raise UninitializableFormat if shared repository are not
 
2210
        compatible the a_bzrdir.
 
2211
        """
 
2212
        raise NotImplementedError(self.initialize)
 
2213
 
 
2214
    def is_supported(self):
 
2215
        """Is this format supported?
 
2216
 
 
2217
        Supported formats must be initializable and openable.
 
2218
        Unsupported formats may not support initialization or committing or 
 
2219
        some other features depending on the reason for not being supported.
 
2220
        """
 
2221
        return True
 
2222
 
 
2223
    def check_conversion_target(self, target_format):
 
2224
        raise NotImplementedError(self.check_conversion_target)
 
2225
 
 
2226
    def open(self, a_bzrdir, _found=False):
 
2227
        """Return an instance of this format for the bzrdir a_bzrdir.
 
2228
        
 
2229
        _found is a private parameter, do not use it.
 
2230
        """
 
2231
        raise NotImplementedError(self.open)
 
2232
 
 
2233
 
 
2234
class MetaDirRepositoryFormat(RepositoryFormat):
 
2235
    """Common base class for the new repositories using the metadir layout."""
 
2236
 
 
2237
    rich_root_data = False
 
2238
    supports_tree_reference = False
 
2239
    supports_external_lookups = False
 
2240
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
2241
 
 
2242
    def __init__(self):
 
2243
        super(MetaDirRepositoryFormat, self).__init__()
 
2244
 
 
2245
    def _create_control_files(self, a_bzrdir):
 
2246
        """Create the required files and the initial control_files object."""
 
2247
        # FIXME: RBC 20060125 don't peek under the covers
 
2248
        # NB: no need to escape relative paths that are url safe.
 
2249
        repository_transport = a_bzrdir.get_repository_transport(self)
 
2250
        control_files = lockable_files.LockableFiles(repository_transport,
 
2251
                                'lock', lockdir.LockDir)
 
2252
        control_files.create_lock()
 
2253
        return control_files
 
2254
 
 
2255
    def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
 
2256
        """Upload the initial blank content."""
 
2257
        control_files = self._create_control_files(a_bzrdir)
 
2258
        control_files.lock_write()
 
2259
        transport = control_files._transport
 
2260
        if shared == True:
 
2261
            utf8_files += [('shared-storage', '')]
 
2262
        try:
 
2263
            transport.mkdir_multi(dirs, mode=a_bzrdir._get_dir_mode())
 
2264
            for (filename, content_stream) in files:
 
2265
                transport.put_file(filename, content_stream,
 
2266
                    mode=a_bzrdir._get_file_mode())
 
2267
            for (filename, content_bytes) in utf8_files:
 
2268
                transport.put_bytes_non_atomic(filename, content_bytes,
 
2269
                    mode=a_bzrdir._get_file_mode())
 
2270
        finally:
 
2271
            control_files.unlock()
 
2272
 
 
2273
 
 
2274
# formats which have no format string are not discoverable
 
2275
# and not independently creatable, so are not registered.  They're 
 
2276
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
2277
# needed, it's constructed directly by the BzrDir.  Non-native formats where
 
2278
# the repository is not separately opened are similar.
 
2279
 
 
2280
format_registry.register_lazy(
 
2281
    'Bazaar-NG Repository format 7',
 
2282
    'bzrlib.repofmt.weaverepo',
 
2283
    'RepositoryFormat7'
 
2284
    )
 
2285
 
 
2286
format_registry.register_lazy(
 
2287
    'Bazaar-NG Knit Repository Format 1',
 
2288
    'bzrlib.repofmt.knitrepo',
 
2289
    'RepositoryFormatKnit1',
 
2290
    )
 
2291
 
 
2292
format_registry.register_lazy(
 
2293
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
 
2294
    'bzrlib.repofmt.knitrepo',
 
2295
    'RepositoryFormatKnit3',
 
2296
    )
 
2297
 
 
2298
format_registry.register_lazy(
 
2299
    'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
 
2300
    'bzrlib.repofmt.knitrepo',
 
2301
    'RepositoryFormatKnit4',
 
2302
    )
 
2303
 
 
2304
# Pack-based formats. There is one format for pre-subtrees, and one for
 
2305
# post-subtrees to allow ease of testing.
 
2306
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
 
2307
format_registry.register_lazy(
 
2308
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
 
2309
    'bzrlib.repofmt.pack_repo',
 
2310
    'RepositoryFormatKnitPack1',
 
2311
    )
 
2312
format_registry.register_lazy(
 
2313
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
 
2314
    'bzrlib.repofmt.pack_repo',
 
2315
    'RepositoryFormatKnitPack3',
 
2316
    )
 
2317
format_registry.register_lazy(
 
2318
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
 
2319
    'bzrlib.repofmt.pack_repo',
 
2320
    'RepositoryFormatKnitPack4',
 
2321
    )
 
2322
format_registry.register_lazy(
 
2323
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
 
2324
    'bzrlib.repofmt.pack_repo',
 
2325
    'RepositoryFormatKnitPack5',
 
2326
    )
 
2327
format_registry.register_lazy(
 
2328
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
 
2329
    'bzrlib.repofmt.pack_repo',
 
2330
    'RepositoryFormatKnitPack5RichRoot',
 
2331
    )
 
2332
format_registry.register_lazy(
 
2333
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
 
2334
    'bzrlib.repofmt.pack_repo',
 
2335
    'RepositoryFormatKnitPack5RichRootBroken',
 
2336
    )
 
2337
format_registry.register_lazy(
 
2338
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
 
2339
    'bzrlib.repofmt.pack_repo',
 
2340
    'RepositoryFormatKnitPack6',
 
2341
    )
 
2342
format_registry.register_lazy(
 
2343
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
 
2344
    'bzrlib.repofmt.pack_repo',
 
2345
    'RepositoryFormatKnitPack6RichRoot',
 
2346
    )
 
2347
 
 
2348
# Development formats. 
 
2349
# 1.7->1.8 go below here
 
2350
format_registry.register_lazy(
 
2351
    "Bazaar development format 2 (needs bzr.dev from before 1.8)\n",
 
2352
    'bzrlib.repofmt.pack_repo',
 
2353
    'RepositoryFormatPackDevelopment2',
 
2354
    )
 
2355
format_registry.register_lazy(
 
2356
    ("Bazaar development format 2 with subtree support "
 
2357
        "(needs bzr.dev from before 1.8)\n"),
 
2358
    'bzrlib.repofmt.pack_repo',
 
2359
    'RepositoryFormatPackDevelopment2Subtree',
 
2360
    )
 
2361
 
 
2362
 
 
2363
class InterRepository(InterObject):
 
2364
    """This class represents operations taking place between two repositories.
 
2365
 
 
2366
    Its instances have methods like copy_content and fetch, and contain
 
2367
    references to the source and target repositories these operations can be 
 
2368
    carried out on.
 
2369
 
 
2370
    Often we will provide convenience methods on 'repository' which carry out
 
2371
    operations with another repository - they will always forward to
 
2372
    InterRepository.get(other).method_name(parameters).
 
2373
    """
 
2374
 
 
2375
    _walk_to_common_revisions_batch_size = 1
 
2376
    _optimisers = []
 
2377
    """The available optimised InterRepository types."""
 
2378
 
 
2379
    def __init__(self, source, target):
 
2380
        InterObject.__init__(self, source, target)
 
2381
        # These two attributes may be overridden by e.g. InterOtherToRemote to
 
2382
        # provide a faster implementation.
 
2383
        self.target_get_graph = self.target.get_graph
 
2384
        self.target_get_parent_map = self.target.get_parent_map
 
2385
 
 
2386
    def copy_content(self, revision_id=None):
 
2387
        raise NotImplementedError(self.copy_content)
 
2388
 
 
2389
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2390
        """Fetch the content required to construct revision_id.
 
2391
 
 
2392
        The content is copied from self.source to self.target.
 
2393
 
 
2394
        :param revision_id: if None all content is copied, if NULL_REVISION no
 
2395
                            content is copied.
 
2396
        :param pb: optional progress bar to use for progress reports. If not
 
2397
                   provided a default one will be created.
 
2398
 
 
2399
        :returns: (copied_revision_count, failures).
 
2400
        """
 
2401
        # Normally we should find a specific InterRepository subclass to do
 
2402
        # the fetch; if nothing else then at least InterSameDataRepository.
 
2403
        # If none of them is suitable it looks like fetching is not possible;
 
2404
        # we try to give a good message why.  _assert_same_model will probably
 
2405
        # give a helpful message; otherwise a generic one.
 
2406
        self._assert_same_model(self.source, self.target)
 
2407
        raise errors.IncompatibleRepositories(self.source, self.target,
 
2408
            "no suitableInterRepository found")
 
2409
 
 
2410
    def _walk_to_common_revisions(self, revision_ids):
 
2411
        """Walk out from revision_ids in source to revisions target has.
 
2412
 
 
2413
        :param revision_ids: The start point for the search.
 
2414
        :return: A set of revision ids.
 
2415
        """
 
2416
        target_graph = self.target_get_graph()
 
2417
        revision_ids = frozenset(revision_ids)
 
2418
        # Fast path for the case where all the revisions are already in the
 
2419
        # target repo.
 
2420
        # (Although this does incur an extra round trip for the
 
2421
        # fairly common case where the target doesn't already have the revision
 
2422
        # we're pushing.)
 
2423
        if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
 
2424
            return graph.SearchResult(revision_ids, set(), 0, set())
 
2425
        missing_revs = set()
 
2426
        source_graph = self.source.get_graph()
 
2427
        # ensure we don't pay silly lookup costs.
 
2428
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
2429
        null_set = frozenset([_mod_revision.NULL_REVISION])
 
2430
        searcher_exhausted = False
 
2431
        while True:
 
2432
            next_revs = set()
 
2433
            ghosts = set()
 
2434
            # Iterate the searcher until we have enough next_revs
 
2435
            while len(next_revs) < self._walk_to_common_revisions_batch_size:
 
2436
                try:
 
2437
                    next_revs_part, ghosts_part = searcher.next_with_ghosts()
 
2438
                    next_revs.update(next_revs_part)
 
2439
                    ghosts.update(ghosts_part)
 
2440
                except StopIteration:
 
2441
                    searcher_exhausted = True
 
2442
                    break
 
2443
            # If there are ghosts in the source graph, and the caller asked for
 
2444
            # them, make sure that they are present in the target.
 
2445
            # We don't care about other ghosts as we can't fetch them and
 
2446
            # haven't been asked to.
 
2447
            ghosts_to_check = set(revision_ids.intersection(ghosts))
 
2448
            revs_to_get = set(next_revs).union(ghosts_to_check)
 
2449
            if revs_to_get:
 
2450
                have_revs = set(target_graph.get_parent_map(revs_to_get))
 
2451
                # we always have NULL_REVISION present.
 
2452
                have_revs = have_revs.union(null_set)
 
2453
                # Check if the target is missing any ghosts we need.
 
2454
                ghosts_to_check.difference_update(have_revs)
 
2455
                if ghosts_to_check:
 
2456
                    # One of the caller's revision_ids is a ghost in both the
 
2457
                    # source and the target.
 
2458
                    raise errors.NoSuchRevision(
 
2459
                        self.source, ghosts_to_check.pop())
 
2460
                missing_revs.update(next_revs - have_revs)
 
2461
                # Because we may have walked past the original stop point, make
 
2462
                # sure everything is stopped
 
2463
                stop_revs = searcher.find_seen_ancestors(have_revs)
 
2464
                searcher.stop_searching_any(stop_revs)
 
2465
            if searcher_exhausted:
 
2466
                break
 
2467
        return searcher.get_result()
 
2468
 
 
2469
    @deprecated_method(one_two)
 
2470
    @needs_read_lock
 
2471
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2472
        """Return the revision ids that source has that target does not.
 
2473
        
 
2474
        These are returned in topological order.
 
2475
 
 
2476
        :param revision_id: only return revision ids included by this
 
2477
                            revision_id.
 
2478
        :param find_ghosts: If True find missing revisions in deep history
 
2479
            rather than just finding the surface difference.
 
2480
        """
 
2481
        return list(self.search_missing_revision_ids(
 
2482
            revision_id, find_ghosts).get_keys())
 
2483
 
 
2484
    @needs_read_lock
 
2485
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2486
        """Return the revision ids that source has that target does not.
 
2487
        
 
2488
        :param revision_id: only return revision ids included by this
 
2489
                            revision_id.
 
2490
        :param find_ghosts: If True find missing revisions in deep history
 
2491
            rather than just finding the surface difference.
 
2492
        :return: A bzrlib.graph.SearchResult.
 
2493
        """
 
2494
        # stop searching at found target revisions.
 
2495
        if not find_ghosts and revision_id is not None:
 
2496
            return self._walk_to_common_revisions([revision_id])
 
2497
        # generic, possibly worst case, slow code path.
 
2498
        target_ids = set(self.target.all_revision_ids())
 
2499
        if revision_id is not None:
 
2500
            source_ids = self.source.get_ancestry(revision_id)
 
2501
            if source_ids[0] is not None:
 
2502
                raise AssertionError()
 
2503
            source_ids.pop(0)
 
2504
        else:
 
2505
            source_ids = self.source.all_revision_ids()
 
2506
        result_set = set(source_ids).difference(target_ids)
 
2507
        return self.source.revision_ids_to_search_result(result_set)
 
2508
 
 
2509
    @staticmethod
 
2510
    def _same_model(source, target):
 
2511
        """True if source and target have the same data representation.
 
2512
        
 
2513
        Note: this is always called on the base class; overriding it in a
 
2514
        subclass will have no effect.
 
2515
        """
 
2516
        try:
 
2517
            InterRepository._assert_same_model(source, target)
 
2518
            return True
 
2519
        except errors.IncompatibleRepositories, e:
 
2520
            return False
 
2521
 
 
2522
    @staticmethod
 
2523
    def _assert_same_model(source, target):
 
2524
        """Raise an exception if two repositories do not use the same model.
 
2525
        """
 
2526
        if source.supports_rich_root() != target.supports_rich_root():
 
2527
            raise errors.IncompatibleRepositories(source, target,
 
2528
                "different rich-root support")
 
2529
        if source._serializer != target._serializer:
 
2530
            raise errors.IncompatibleRepositories(source, target,
 
2531
                "different serializers")
 
2532
 
 
2533
 
 
2534
class InterSameDataRepository(InterRepository):
 
2535
    """Code for converting between repositories that represent the same data.
 
2536
    
 
2537
    Data format and model must match for this to work.
 
2538
    """
 
2539
 
 
2540
    @classmethod
 
2541
    def _get_repo_format_to_test(self):
 
2542
        """Repository format for testing with.
 
2543
        
 
2544
        InterSameData can pull from subtree to subtree and from non-subtree to
 
2545
        non-subtree, so we test this with the richest repository format.
 
2546
        """
 
2547
        from bzrlib.repofmt import knitrepo
 
2548
        return knitrepo.RepositoryFormatKnit3()
 
2549
 
 
2550
    @staticmethod
 
2551
    def is_compatible(source, target):
 
2552
        return InterRepository._same_model(source, target)
 
2553
 
 
2554
    @needs_write_lock
 
2555
    def copy_content(self, revision_id=None):
 
2556
        """Make a complete copy of the content in self into destination.
 
2557
 
 
2558
        This copies both the repository's revision data, and configuration information
 
2559
        such as the make_working_trees setting.
 
2560
        
 
2561
        This is a destructive operation! Do not use it on existing 
 
2562
        repositories.
 
2563
 
 
2564
        :param revision_id: Only copy the content needed to construct
 
2565
                            revision_id and its parents.
 
2566
        """
 
2567
        try:
 
2568
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2569
        except NotImplementedError:
 
2570
            pass
 
2571
        # but don't bother fetching if we have the needed data now.
 
2572
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
2573
            self.target.has_revision(revision_id)):
 
2574
            return
 
2575
        self.target.fetch(self.source, revision_id=revision_id)
 
2576
 
 
2577
    @needs_write_lock
 
2578
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2579
        """See InterRepository.fetch()."""
 
2580
        from bzrlib.fetch import RepoFetcher
 
2581
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2582
               self.source, self.source._format, self.target,
 
2583
               self.target._format)
 
2584
        f = RepoFetcher(to_repository=self.target,
 
2585
                               from_repository=self.source,
 
2586
                               last_revision=revision_id,
 
2587
                               pb=pb, find_ghosts=find_ghosts)
 
2588
        return f.count_copied, f.failed_revisions
 
2589
 
 
2590
 
 
2591
class InterWeaveRepo(InterSameDataRepository):
 
2592
    """Optimised code paths between Weave based repositories.
 
2593
    
 
2594
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
 
2595
    implemented lazy inter-object optimisation.
 
2596
    """
 
2597
 
 
2598
    @classmethod
 
2599
    def _get_repo_format_to_test(self):
 
2600
        from bzrlib.repofmt import weaverepo
 
2601
        return weaverepo.RepositoryFormat7()
 
2602
 
 
2603
    @staticmethod
 
2604
    def is_compatible(source, target):
 
2605
        """Be compatible with known Weave formats.
 
2606
        
 
2607
        We don't test for the stores being of specific types because that
 
2608
        could lead to confusing results, and there is no need to be 
 
2609
        overly general.
 
2610
        """
 
2611
        from bzrlib.repofmt.weaverepo import (
 
2612
                RepositoryFormat5,
 
2613
                RepositoryFormat6,
 
2614
                RepositoryFormat7,
 
2615
                )
 
2616
        try:
 
2617
            return (isinstance(source._format, (RepositoryFormat5,
 
2618
                                                RepositoryFormat6,
 
2619
                                                RepositoryFormat7)) and
 
2620
                    isinstance(target._format, (RepositoryFormat5,
 
2621
                                                RepositoryFormat6,
 
2622
                                                RepositoryFormat7)))
 
2623
        except AttributeError:
 
2624
            return False
 
2625
    
 
2626
    @needs_write_lock
 
2627
    def copy_content(self, revision_id=None):
 
2628
        """See InterRepository.copy_content()."""
 
2629
        # weave specific optimised path:
 
2630
        try:
 
2631
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2632
        except (errors.RepositoryUpgradeRequired, NotImplemented):
 
2633
            pass
 
2634
        # FIXME do not peek!
 
2635
        if self.source._transport.listable():
 
2636
            pb = ui.ui_factory.nested_progress_bar()
 
2637
            try:
 
2638
                self.target.texts.insert_record_stream(
 
2639
                    self.source.texts.get_record_stream(
 
2640
                        self.source.texts.keys(), 'topological', False))
 
2641
                pb.update('copying inventory', 0, 1)
 
2642
                self.target.inventories.insert_record_stream(
 
2643
                    self.source.inventories.get_record_stream(
 
2644
                        self.source.inventories.keys(), 'topological', False))
 
2645
                self.target.signatures.insert_record_stream(
 
2646
                    self.source.signatures.get_record_stream(
 
2647
                        self.source.signatures.keys(),
 
2648
                        'unordered', True))
 
2649
                self.target.revisions.insert_record_stream(
 
2650
                    self.source.revisions.get_record_stream(
 
2651
                        self.source.revisions.keys(),
 
2652
                        'topological', True))
 
2653
            finally:
 
2654
                pb.finished()
 
2655
        else:
 
2656
            self.target.fetch(self.source, revision_id=revision_id)
 
2657
 
 
2658
    @needs_write_lock
 
2659
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2660
        """See InterRepository.fetch()."""
 
2661
        from bzrlib.fetch import RepoFetcher
 
2662
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2663
               self.source, self.source._format, self.target, self.target._format)
 
2664
        f = RepoFetcher(to_repository=self.target,
 
2665
                               from_repository=self.source,
 
2666
                               last_revision=revision_id,
 
2667
                               pb=pb, find_ghosts=find_ghosts)
 
2668
        return f.count_copied, f.failed_revisions
 
2669
 
 
2670
    @needs_read_lock
 
2671
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2672
        """See InterRepository.missing_revision_ids()."""
 
2673
        # we want all revisions to satisfy revision_id in source.
 
2674
        # but we don't want to stat every file here and there.
 
2675
        # we want then, all revisions other needs to satisfy revision_id 
 
2676
        # checked, but not those that we have locally.
 
2677
        # so the first thing is to get a subset of the revisions to 
 
2678
        # satisfy revision_id in source, and then eliminate those that
 
2679
        # we do already have. 
 
2680
        # this is slow on high latency connection to self, but as as this
 
2681
        # disk format scales terribly for push anyway due to rewriting 
 
2682
        # inventory.weave, this is considered acceptable.
 
2683
        # - RBC 20060209
 
2684
        if revision_id is not None:
 
2685
            source_ids = self.source.get_ancestry(revision_id)
 
2686
            if source_ids[0] is not None:
 
2687
                raise AssertionError()
 
2688
            source_ids.pop(0)
 
2689
        else:
 
2690
            source_ids = self.source._all_possible_ids()
 
2691
        source_ids_set = set(source_ids)
 
2692
        # source_ids is the worst possible case we may need to pull.
 
2693
        # now we want to filter source_ids against what we actually
 
2694
        # have in target, but don't try to check for existence where we know
 
2695
        # we do not have a revision as that would be pointless.
 
2696
        target_ids = set(self.target._all_possible_ids())
 
2697
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
2698
        actually_present_revisions = set(
 
2699
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2700
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2701
        if revision_id is not None:
 
2702
            # we used get_ancestry to determine source_ids then we are assured all
 
2703
            # revisions referenced are present as they are installed in topological order.
 
2704
            # and the tip revision was validated by get_ancestry.
 
2705
            result_set = required_revisions
 
2706
        else:
 
2707
            # if we just grabbed the possibly available ids, then 
 
2708
            # we only have an estimate of whats available and need to validate
 
2709
            # that against the revision records.
 
2710
            result_set = set(
 
2711
                self.source._eliminate_revisions_not_present(required_revisions))
 
2712
        return self.source.revision_ids_to_search_result(result_set)
 
2713
 
 
2714
 
 
2715
class InterKnitRepo(InterSameDataRepository):
 
2716
    """Optimised code paths between Knit based repositories."""
 
2717
 
 
2718
    @classmethod
 
2719
    def _get_repo_format_to_test(self):
 
2720
        from bzrlib.repofmt import knitrepo
 
2721
        return knitrepo.RepositoryFormatKnit1()
 
2722
 
 
2723
    @staticmethod
 
2724
    def is_compatible(source, target):
 
2725
        """Be compatible with known Knit formats.
 
2726
        
 
2727
        We don't test for the stores being of specific types because that
 
2728
        could lead to confusing results, and there is no need to be 
 
2729
        overly general.
 
2730
        """
 
2731
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
 
2732
        try:
 
2733
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
2734
                isinstance(target._format, RepositoryFormatKnit))
 
2735
        except AttributeError:
 
2736
            return False
 
2737
        return are_knits and InterRepository._same_model(source, target)
 
2738
 
 
2739
    @needs_write_lock
 
2740
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2741
        """See InterRepository.fetch()."""
 
2742
        from bzrlib.fetch import RepoFetcher
 
2743
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2744
               self.source, self.source._format, self.target, self.target._format)
 
2745
        f = RepoFetcher(to_repository=self.target,
 
2746
                            from_repository=self.source,
 
2747
                            last_revision=revision_id,
 
2748
                            pb=pb, find_ghosts=find_ghosts)
 
2749
        return f.count_copied, f.failed_revisions
 
2750
 
 
2751
    @needs_read_lock
 
2752
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2753
        """See InterRepository.missing_revision_ids()."""
 
2754
        if revision_id is not None:
 
2755
            source_ids = self.source.get_ancestry(revision_id)
 
2756
            if source_ids[0] is not None:
 
2757
                raise AssertionError()
 
2758
            source_ids.pop(0)
 
2759
        else:
 
2760
            source_ids = self.source.all_revision_ids()
 
2761
        source_ids_set = set(source_ids)
 
2762
        # source_ids is the worst possible case we may need to pull.
 
2763
        # now we want to filter source_ids against what we actually
 
2764
        # have in target, but don't try to check for existence where we know
 
2765
        # we do not have a revision as that would be pointless.
 
2766
        target_ids = set(self.target.all_revision_ids())
 
2767
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
2768
        actually_present_revisions = set(
 
2769
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2770
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2771
        if revision_id is not None:
 
2772
            # we used get_ancestry to determine source_ids then we are assured all
 
2773
            # revisions referenced are present as they are installed in topological order.
 
2774
            # and the tip revision was validated by get_ancestry.
 
2775
            result_set = required_revisions
 
2776
        else:
 
2777
            # if we just grabbed the possibly available ids, then 
 
2778
            # we only have an estimate of whats available and need to validate
 
2779
            # that against the revision records.
 
2780
            result_set = set(
 
2781
                self.source._eliminate_revisions_not_present(required_revisions))
 
2782
        return self.source.revision_ids_to_search_result(result_set)
 
2783
 
 
2784
 
 
2785
class InterPackRepo(InterSameDataRepository):
 
2786
    """Optimised code paths between Pack based repositories."""
 
2787
 
 
2788
    @classmethod
 
2789
    def _get_repo_format_to_test(self):
 
2790
        from bzrlib.repofmt import pack_repo
 
2791
        return pack_repo.RepositoryFormatKnitPack1()
 
2792
 
 
2793
    @staticmethod
 
2794
    def is_compatible(source, target):
 
2795
        """Be compatible with known Pack formats.
 
2796
        
 
2797
        We don't test for the stores being of specific types because that
 
2798
        could lead to confusing results, and there is no need to be 
 
2799
        overly general.
 
2800
        """
 
2801
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
 
2802
        try:
 
2803
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
 
2804
                isinstance(target._format, RepositoryFormatPack))
 
2805
        except AttributeError:
 
2806
            return False
 
2807
        return are_packs and InterRepository._same_model(source, target)
 
2808
 
 
2809
    @needs_write_lock
 
2810
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2811
        """See InterRepository.fetch()."""
 
2812
        if (len(self.source._fallback_repositories) > 0 or
 
2813
            len(self.target._fallback_repositories) > 0):
 
2814
            # The pack layer is not aware of fallback repositories, so when
 
2815
            # fetching from a stacked repository or into a stacked repository
 
2816
            # we use the generic fetch logic which uses the VersionedFiles
 
2817
            # attributes on repository.
 
2818
            from bzrlib.fetch import RepoFetcher
 
2819
            fetcher = RepoFetcher(self.target, self.source, revision_id,
 
2820
                                  pb, find_ghosts)
 
2821
            return fetcher.count_copied, fetcher.failed_revisions
 
2822
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2823
               self.source, self.source._format, self.target, self.target._format)
 
2824
        self.count_copied = 0
 
2825
        if revision_id is None:
 
2826
            # TODO:
 
2827
            # everything to do - use pack logic
 
2828
            # to fetch from all packs to one without
 
2829
            # inventory parsing etc, IFF nothing to be copied is in the target.
 
2830
            # till then:
 
2831
            source_revision_ids = frozenset(self.source.all_revision_ids())
 
2832
            revision_ids = source_revision_ids - \
 
2833
                frozenset(self.target_get_parent_map(source_revision_ids))
 
2834
            revision_keys = [(revid,) for revid in revision_ids]
 
2835
            target_pack_collection = self._get_target_pack_collection()
 
2836
            index = target_pack_collection.revision_index.combined_index
 
2837
            present_revision_ids = set(item[1][0] for item in
 
2838
                index.iter_entries(revision_keys))
 
2839
            revision_ids = set(revision_ids) - present_revision_ids
 
2840
            # implementing the TODO will involve:
 
2841
            # - detecting when all of a pack is selected
 
2842
            # - avoiding as much as possible pre-selection, so the
 
2843
            # more-core routines such as create_pack_from_packs can filter in
 
2844
            # a just-in-time fashion. (though having a HEADS list on a
 
2845
            # repository might make this a lot easier, because we could
 
2846
            # sensibly detect 'new revisions' without doing a full index scan.
 
2847
        elif _mod_revision.is_null(revision_id):
 
2848
            # nothing to do:
 
2849
            return (0, [])
 
2850
        else:
 
2851
            try:
 
2852
                revision_ids = self.search_missing_revision_ids(revision_id,
 
2853
                    find_ghosts=find_ghosts).get_keys()
 
2854
            except errors.NoSuchRevision:
 
2855
                raise errors.InstallFailed([revision_id])
 
2856
            if len(revision_ids) == 0:
 
2857
                return (0, [])
 
2858
        return self._pack(self.source, self.target, revision_ids)
 
2859
 
 
2860
    def _pack(self, source, target, revision_ids):
 
2861
        from bzrlib.repofmt.pack_repo import Packer
 
2862
        target_pack_collection = self._get_target_pack_collection()
 
2863
        packs = source._pack_collection.all_packs()
 
2864
        pack = Packer(target_pack_collection, packs, '.fetch',
 
2865
            revision_ids).pack()
 
2866
        if pack is not None:
 
2867
            target_pack_collection._save_pack_names()
 
2868
            copied_revs = pack.get_revision_count()
 
2869
            # Trigger an autopack. This may duplicate effort as we've just done
 
2870
            # a pack creation, but for now it is simpler to think about as
 
2871
            # 'upload data, then repack if needed'.
 
2872
            self._autopack()
 
2873
            return (copied_revs, [])
 
2874
        else:
 
2875
            return (0, [])
 
2876
 
 
2877
    def _autopack(self):
 
2878
        self.target._pack_collection.autopack()
 
2879
        
 
2880
    def _get_target_pack_collection(self):
 
2881
        return self.target._pack_collection
 
2882
 
 
2883
    @needs_read_lock
 
2884
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2885
        """See InterRepository.missing_revision_ids().
 
2886
        
 
2887
        :param find_ghosts: Find ghosts throughout the ancestry of
 
2888
            revision_id.
 
2889
        """
 
2890
        if not find_ghosts and revision_id is not None:
 
2891
            return self._walk_to_common_revisions([revision_id])
 
2892
        elif revision_id is not None:
 
2893
            # Find ghosts: search for revisions pointing from one repository to
 
2894
            # the other, and vice versa, anywhere in the history of revision_id.
 
2895
            graph = self.target_get_graph(other_repository=self.source)
 
2896
            searcher = graph._make_breadth_first_searcher([revision_id])
 
2897
            found_ids = set()
 
2898
            while True:
 
2899
                try:
 
2900
                    next_revs, ghosts = searcher.next_with_ghosts()
 
2901
                except StopIteration:
 
2902
                    break
 
2903
                if revision_id in ghosts:
 
2904
                    raise errors.NoSuchRevision(self.source, revision_id)
 
2905
                found_ids.update(next_revs)
 
2906
                found_ids.update(ghosts)
 
2907
            found_ids = frozenset(found_ids)
 
2908
            # Double query here: should be able to avoid this by changing the
 
2909
            # graph api further.
 
2910
            result_set = found_ids - frozenset(
 
2911
                self.target_get_parent_map(found_ids))
 
2912
        else:
 
2913
            source_ids = self.source.all_revision_ids()
 
2914
            # source_ids is the worst possible case we may need to pull.
 
2915
            # now we want to filter source_ids against what we actually
 
2916
            # have in target, but don't try to check for existence where we know
 
2917
            # we do not have a revision as that would be pointless.
 
2918
            target_ids = set(self.target.all_revision_ids())
 
2919
            result_set = set(source_ids).difference(target_ids)
 
2920
        return self.source.revision_ids_to_search_result(result_set)
 
2921
 
 
2922
 
 
2923
class InterModel1and2(InterRepository):
 
2924
 
 
2925
    @classmethod
 
2926
    def _get_repo_format_to_test(self):
 
2927
        return None
 
2928
 
 
2929
    @staticmethod
 
2930
    def is_compatible(source, target):
 
2931
        if not source.supports_rich_root() and target.supports_rich_root():
 
2932
            return True
 
2933
        else:
 
2934
            return False
 
2935
 
 
2936
    @needs_write_lock
 
2937
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2938
        """See InterRepository.fetch()."""
 
2939
        from bzrlib.fetch import Model1toKnit2Fetcher
 
2940
        f = Model1toKnit2Fetcher(to_repository=self.target,
 
2941
                                 from_repository=self.source,
 
2942
                                 last_revision=revision_id,
 
2943
                                 pb=pb, find_ghosts=find_ghosts)
 
2944
        return f.count_copied, f.failed_revisions
 
2945
 
 
2946
    @needs_write_lock
 
2947
    def copy_content(self, revision_id=None):
 
2948
        """Make a complete copy of the content in self into destination.
 
2949
        
 
2950
        This is a destructive operation! Do not use it on existing 
 
2951
        repositories.
 
2952
 
 
2953
        :param revision_id: Only copy the content needed to construct
 
2954
                            revision_id and its parents.
 
2955
        """
 
2956
        try:
 
2957
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2958
        except NotImplementedError:
 
2959
            pass
 
2960
        # but don't bother fetching if we have the needed data now.
 
2961
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
2962
            self.target.has_revision(revision_id)):
 
2963
            return
 
2964
        self.target.fetch(self.source, revision_id=revision_id)
 
2965
 
 
2966
 
 
2967
class InterKnit1and2(InterKnitRepo):
 
2968
 
 
2969
    @classmethod
 
2970
    def _get_repo_format_to_test(self):
 
2971
        return None
 
2972
 
 
2973
    @staticmethod
 
2974
    def is_compatible(source, target):
 
2975
        """Be compatible with Knit1 source and Knit3 target"""
 
2976
        try:
 
2977
            from bzrlib.repofmt.knitrepo import (
 
2978
                RepositoryFormatKnit1,
 
2979
                RepositoryFormatKnit3,
 
2980
                )
 
2981
            from bzrlib.repofmt.pack_repo import (
 
2982
                RepositoryFormatKnitPack1,
 
2983
                RepositoryFormatKnitPack3,
 
2984
                RepositoryFormatKnitPack4,
 
2985
                RepositoryFormatKnitPack5,
 
2986
                RepositoryFormatKnitPack5RichRoot,
 
2987
                RepositoryFormatKnitPack6,
 
2988
                RepositoryFormatKnitPack6RichRoot,
 
2989
                RepositoryFormatPackDevelopment2,
 
2990
                RepositoryFormatPackDevelopment2Subtree,
 
2991
                )
 
2992
            norichroot = (
 
2993
                RepositoryFormatKnit1,            # no rr, no subtree
 
2994
                RepositoryFormatKnitPack1,        # no rr, no subtree
 
2995
                RepositoryFormatPackDevelopment2, # no rr, no subtree
 
2996
                RepositoryFormatKnitPack5,        # no rr, no subtree
 
2997
                RepositoryFormatKnitPack6,        # no rr, no subtree
 
2998
                )
 
2999
            richroot = (
 
3000
                RepositoryFormatKnit3,            # rr, subtree
 
3001
                RepositoryFormatKnitPack3,        # rr, subtree
 
3002
                RepositoryFormatKnitPack4,        # rr, no subtree
 
3003
                RepositoryFormatKnitPack5RichRoot,# rr, no subtree
 
3004
                RepositoryFormatKnitPack6RichRoot,# rr, no subtree
 
3005
                RepositoryFormatPackDevelopment2Subtree, # rr, subtree
 
3006
                )
 
3007
            for format in norichroot:
 
3008
                if format.rich_root_data:
 
3009
                    raise AssertionError('Format %s is a rich-root format'
 
3010
                        ' but is included in the non-rich-root list'
 
3011
                        % (format,))
 
3012
            for format in richroot:
 
3013
                if not format.rich_root_data:
 
3014
                    raise AssertionError('Format %s is not a rich-root format'
 
3015
                        ' but is included in the rich-root list'
 
3016
                        % (format,))
 
3017
            # TODO: One alternative is to just check format.rich_root_data,
 
3018
            #       instead of keeping membership lists. However, the formats
 
3019
            #       *also* have to use the same 'Knit' style of storage
 
3020
            #       (line-deltas, fulltexts, etc.)
 
3021
            return (isinstance(source._format, norichroot) and
 
3022
                    isinstance(target._format, richroot))
 
3023
        except AttributeError:
 
3024
            return False
 
3025
 
 
3026
    @needs_write_lock
 
3027
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3028
        """See InterRepository.fetch()."""
 
3029
        from bzrlib.fetch import Knit1to2Fetcher
 
3030
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
3031
               self.source, self.source._format, self.target, 
 
3032
               self.target._format)
 
3033
        f = Knit1to2Fetcher(to_repository=self.target,
 
3034
                            from_repository=self.source,
 
3035
                            last_revision=revision_id,
 
3036
                            pb=pb, find_ghosts=find_ghosts)
 
3037
        return f.count_copied, f.failed_revisions
 
3038
 
 
3039
 
 
3040
class InterDifferingSerializer(InterKnitRepo):
 
3041
 
 
3042
    @classmethod
 
3043
    def _get_repo_format_to_test(self):
 
3044
        return None
 
3045
 
 
3046
    @staticmethod
 
3047
    def is_compatible(source, target):
 
3048
        """Be compatible with Knit2 source and Knit3 target"""
 
3049
        if source.supports_rich_root() != target.supports_rich_root():
 
3050
            return False
 
3051
        # Ideally, we'd support fetching if the source had no tree references
 
3052
        # even if it supported them...
 
3053
        if (getattr(source, '_format.supports_tree_reference', False) and
 
3054
            not getattr(target, '_format.supports_tree_reference', False)):
 
3055
            return False
 
3056
        return True
 
3057
 
 
3058
    @needs_write_lock
 
3059
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3060
        """See InterRepository.fetch()."""
 
3061
        revision_ids = self.target.search_missing_revision_ids(self.source,
 
3062
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3063
        revision_ids = tsort.topo_sort(
 
3064
            self.source.get_graph().get_parent_map(revision_ids))
 
3065
        def revisions_iterator():
 
3066
            for current_revision_id in revision_ids:
 
3067
                revision = self.source.get_revision(current_revision_id)
 
3068
                tree = self.source.revision_tree(current_revision_id)
 
3069
                try:
 
3070
                    signature = self.source.get_signature_text(
 
3071
                        current_revision_id)
 
3072
                except errors.NoSuchRevision:
 
3073
                    signature = None
 
3074
                yield revision, tree, signature
 
3075
        if pb is None:
 
3076
            my_pb = ui.ui_factory.nested_progress_bar()
 
3077
            pb = my_pb
 
3078
        else:
 
3079
            my_pb = None
 
3080
        try:
 
3081
            install_revisions(self.target, revisions_iterator(),
 
3082
                              len(revision_ids), pb)
 
3083
        finally:
 
3084
            if my_pb is not None:
 
3085
                my_pb.finished()
 
3086
        return len(revision_ids), 0
 
3087
 
 
3088
 
 
3089
class InterOtherToRemote(InterRepository):
 
3090
    """An InterRepository that simply delegates to the 'real' InterRepository
 
3091
    calculated for (source, target._real_repository).
 
3092
    """
 
3093
 
 
3094
    _walk_to_common_revisions_batch_size = 50
 
3095
 
 
3096
    def __init__(self, source, target):
 
3097
        InterRepository.__init__(self, source, target)
 
3098
        self._real_inter = None
 
3099
 
 
3100
    @staticmethod
 
3101
    def is_compatible(source, target):
 
3102
        if isinstance(target, remote.RemoteRepository):
 
3103
            return True
 
3104
        return False
 
3105
 
 
3106
    def _ensure_real_inter(self):
 
3107
        if self._real_inter is None:
 
3108
            self.target._ensure_real()
 
3109
            real_target = self.target._real_repository
 
3110
            self._real_inter = InterRepository.get(self.source, real_target)
 
3111
            # Make _real_inter use the RemoteRepository for get_parent_map
 
3112
            self._real_inter.target_get_graph = self.target.get_graph
 
3113
            self._real_inter.target_get_parent_map = self.target.get_parent_map
 
3114
    
 
3115
    def copy_content(self, revision_id=None):
 
3116
        self._ensure_real_inter()
 
3117
        self._real_inter.copy_content(revision_id=revision_id)
 
3118
 
 
3119
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3120
        self._ensure_real_inter()
 
3121
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
 
3122
            find_ghosts=find_ghosts)
 
3123
 
 
3124
    @classmethod
 
3125
    def _get_repo_format_to_test(self):
 
3126
        return None
 
3127
 
 
3128
 
 
3129
class InterRemoteToOther(InterRepository):
 
3130
 
 
3131
    def __init__(self, source, target):
 
3132
        InterRepository.__init__(self, source, target)
 
3133
        self._real_inter = None
 
3134
 
 
3135
    @staticmethod
 
3136
    def is_compatible(source, target):
 
3137
        if not isinstance(source, remote.RemoteRepository):
 
3138
            return False
 
3139
        # Is source's model compatible with target's model?
 
3140
        source._ensure_real()
 
3141
        real_source = source._real_repository
 
3142
        if isinstance(real_source, remote.RemoteRepository):
 
3143
            raise NotImplementedError(
 
3144
                "We don't support remote repos backed by remote repos yet.")
 
3145
        return InterRepository._same_model(real_source, target)
 
3146
 
 
3147
    def _ensure_real_inter(self):
 
3148
        if self._real_inter is None:
 
3149
            self.source._ensure_real()
 
3150
            real_source = self.source._real_repository
 
3151
            self._real_inter = InterRepository.get(real_source, self.target)
 
3152
    
 
3153
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
3154
        self._ensure_real_inter()
 
3155
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
 
3156
            find_ghosts=find_ghosts)
 
3157
 
 
3158
    def copy_content(self, revision_id=None):
 
3159
        self._ensure_real_inter()
 
3160
        self._real_inter.copy_content(revision_id=revision_id)
 
3161
 
 
3162
    @classmethod
 
3163
    def _get_repo_format_to_test(self):
 
3164
        return None
 
3165
 
 
3166
 
 
3167
 
 
3168
class InterPackToRemotePack(InterPackRepo):
 
3169
    """A specialisation of InterPackRepo for a target that is a
 
3170
    RemoteRepository.
 
3171
 
 
3172
    This will use the get_parent_map RPC rather than plain readvs, and also
 
3173
    uses an RPC for autopacking.
 
3174
    """
 
3175
 
 
3176
    _walk_to_common_revisions_batch_size = 50
 
3177
 
 
3178
    @staticmethod
 
3179
    def is_compatible(source, target):
 
3180
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
 
3181
        if isinstance(source._format, RepositoryFormatPack):
 
3182
            if isinstance(target, remote.RemoteRepository):
 
3183
                target._ensure_real()
 
3184
                if isinstance(target._real_repository._format,
 
3185
                              RepositoryFormatPack):
 
3186
                    if InterRepository._same_model(source, target):
 
3187
                        return True
 
3188
        return False
 
3189
    
 
3190
    def _autopack(self):
 
3191
        self.target.autopack()
 
3192
        
 
3193
    def _get_target_pack_collection(self):
 
3194
        return self.target._real_repository._pack_collection
 
3195
 
 
3196
    @classmethod
 
3197
    def _get_repo_format_to_test(self):
 
3198
        return None
 
3199
 
 
3200
 
 
3201
InterRepository.register_optimiser(InterDifferingSerializer)
 
3202
InterRepository.register_optimiser(InterSameDataRepository)
 
3203
InterRepository.register_optimiser(InterWeaveRepo)
 
3204
InterRepository.register_optimiser(InterKnitRepo)
 
3205
InterRepository.register_optimiser(InterModel1and2)
 
3206
InterRepository.register_optimiser(InterKnit1and2)
 
3207
InterRepository.register_optimiser(InterPackRepo)
 
3208
InterRepository.register_optimiser(InterOtherToRemote)
 
3209
InterRepository.register_optimiser(InterRemoteToOther)
 
3210
InterRepository.register_optimiser(InterPackToRemotePack)
 
3211
 
 
3212
 
 
3213
class CopyConverter(object):
 
3214
    """A repository conversion tool which just performs a copy of the content.
 
3215
    
 
3216
    This is slow but quite reliable.
 
3217
    """
 
3218
 
 
3219
    def __init__(self, target_format):
 
3220
        """Create a CopyConverter.
 
3221
 
 
3222
        :param target_format: The format the resulting repository should be.
 
3223
        """
 
3224
        self.target_format = target_format
 
3225
        
 
3226
    def convert(self, repo, pb):
 
3227
        """Perform the conversion of to_convert, giving feedback via pb.
 
3228
 
 
3229
        :param to_convert: The disk object to convert.
 
3230
        :param pb: a progress bar to use for progress information.
 
3231
        """
 
3232
        self.pb = pb
 
3233
        self.count = 0
 
3234
        self.total = 4
 
3235
        # this is only useful with metadir layouts - separated repo content.
 
3236
        # trigger an assertion if not such
 
3237
        repo._format.get_format_string()
 
3238
        self.repo_dir = repo.bzrdir
 
3239
        self.step('Moving repository to repository.backup')
 
3240
        self.repo_dir.transport.move('repository', 'repository.backup')
 
3241
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
 
3242
        repo._format.check_conversion_target(self.target_format)
 
3243
        self.source_repo = repo._format.open(self.repo_dir,
 
3244
            _found=True,
 
3245
            _override_transport=backup_transport)
 
3246
        self.step('Creating new repository')
 
3247
        converted = self.target_format.initialize(self.repo_dir,
 
3248
                                                  self.source_repo.is_shared())
 
3249
        converted.lock_write()
 
3250
        try:
 
3251
            self.step('Copying content into repository.')
 
3252
            self.source_repo.copy_content_into(converted)
 
3253
        finally:
 
3254
            converted.unlock()
 
3255
        self.step('Deleting old repository content.')
 
3256
        self.repo_dir.transport.delete_tree('repository.backup')
 
3257
        self.pb.note('repository converted')
 
3258
 
 
3259
    def step(self, message):
 
3260
        """Update the pb by a step."""
 
3261
        self.count +=1
 
3262
        self.pb.update(message, self.count, self.total)
 
3263
 
 
3264
 
 
3265
_unescape_map = {
 
3266
    'apos':"'",
 
3267
    'quot':'"',
 
3268
    'amp':'&',
 
3269
    'lt':'<',
 
3270
    'gt':'>'
 
3271
}
 
3272
 
 
3273
 
 
3274
def _unescaper(match, _map=_unescape_map):
 
3275
    code = match.group(1)
 
3276
    try:
 
3277
        return _map[code]
 
3278
    except KeyError:
 
3279
        if not code.startswith('#'):
 
3280
            raise
 
3281
        return unichr(int(code[1:])).encode('utf8')
 
3282
 
 
3283
 
 
3284
_unescape_re = None
 
3285
 
 
3286
 
 
3287
def _unescape_xml(data):
 
3288
    """Unescape predefined XML entities in a string of data."""
 
3289
    global _unescape_re
 
3290
    if _unescape_re is None:
 
3291
        _unescape_re = re.compile('\&([^;]*);')
 
3292
    return _unescape_re.sub(_unescaper, data)
 
3293
 
 
3294
 
 
3295
class _VersionedFileChecker(object):
 
3296
 
 
3297
    def __init__(self, repository):
 
3298
        self.repository = repository
 
3299
        self.text_index = self.repository._generate_text_key_index()
 
3300
    
 
3301
    def calculate_file_version_parents(self, text_key):
 
3302
        """Calculate the correct parents for a file version according to
 
3303
        the inventories.
 
3304
        """
 
3305
        parent_keys = self.text_index[text_key]
 
3306
        if parent_keys == [_mod_revision.NULL_REVISION]:
 
3307
            return ()
 
3308
        return tuple(parent_keys)
 
3309
 
 
3310
    def check_file_version_parents(self, texts, progress_bar=None):
 
3311
        """Check the parents stored in a versioned file are correct.
 
3312
 
 
3313
        It also detects file versions that are not referenced by their
 
3314
        corresponding revision's inventory.
 
3315
 
 
3316
        :returns: A tuple of (wrong_parents, dangling_file_versions).
 
3317
            wrong_parents is a dict mapping {revision_id: (stored_parents,
 
3318
            correct_parents)} for each revision_id where the stored parents
 
3319
            are not correct.  dangling_file_versions is a set of (file_id,
 
3320
            revision_id) tuples for versions that are present in this versioned
 
3321
            file, but not used by the corresponding inventory.
 
3322
        """
 
3323
        wrong_parents = {}
 
3324
        self.file_ids = set([file_id for file_id, _ in
 
3325
            self.text_index.iterkeys()])
 
3326
        # text keys is now grouped by file_id
 
3327
        n_weaves = len(self.file_ids)
 
3328
        files_in_revisions = {}
 
3329
        revisions_of_files = {}
 
3330
        n_versions = len(self.text_index)
 
3331
        progress_bar.update('loading text store', 0, n_versions)
 
3332
        parent_map = self.repository.texts.get_parent_map(self.text_index)
 
3333
        # On unlistable transports this could well be empty/error...
 
3334
        text_keys = self.repository.texts.keys()
 
3335
        unused_keys = frozenset(text_keys) - set(self.text_index)
 
3336
        for num, key in enumerate(self.text_index.iterkeys()):
 
3337
            if progress_bar is not None:
 
3338
                progress_bar.update('checking text graph', num, n_versions)
 
3339
            correct_parents = self.calculate_file_version_parents(key)
 
3340
            try:
 
3341
                knit_parents = parent_map[key]
 
3342
            except errors.RevisionNotPresent:
 
3343
                # Missing text!
 
3344
                knit_parents = None
 
3345
            if correct_parents != knit_parents:
 
3346
                wrong_parents[key] = (knit_parents, correct_parents)
 
3347
        return wrong_parents, unused_keys
 
3348
 
 
3349
 
 
3350
def _old_get_graph(repository, revision_id):
 
3351
    """DO NOT USE. That is all. I'm serious."""
 
3352
    graph = repository.get_graph()
 
3353
    revision_graph = dict(((key, value) for key, value in
 
3354
        graph.iter_ancestry([revision_id]) if value is not None))
 
3355
    return _strip_NULL_ghosts(revision_graph)
 
3356
 
 
3357
 
 
3358
def _strip_NULL_ghosts(revision_graph):
 
3359
    """Also don't use this. more compatibility code for unmigrated clients."""
 
3360
    # Filter ghosts, and null:
 
3361
    if _mod_revision.NULL_REVISION in revision_graph:
 
3362
        del revision_graph[_mod_revision.NULL_REVISION]
 
3363
    for key, parents in revision_graph.items():
 
3364
        revision_graph[key] = tuple(parent for parent in parents if parent
 
3365
            in revision_graph)
 
3366
    return revision_graph