~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-03 08:49:38 UTC
  • mfrom: (3111.1.31 175524)
  • mto: This revision was merged to the branch mainline in revision 3158.
  • Revision ID: v.ladeuil+lp@free.fr-20080103084938-7kvurk5uvde2ui54
Fix bug #175524, http test servers are 1.1 compliant

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
lazy_import(globals(), """
21
21
import re
22
22
import time
23
 
import unittest
24
23
 
25
24
from bzrlib import (
26
25
    bzrdir,
27
26
    check,
 
27
    debug,
 
28
    deprecated_graph,
28
29
    errors,
29
30
    generate_ids,
30
31
    gpg,
32
33
    lazy_regex,
33
34
    lockable_files,
34
35
    lockdir,
 
36
    lru_cache,
35
37
    osutils,
36
38
    registry,
37
39
    remote,
38
40
    revision as _mod_revision,
39
41
    symbol_versioning,
40
42
    transactions,
 
43
    tsort,
41
44
    ui,
42
45
    )
 
46
from bzrlib.bundle import serializer
43
47
from bzrlib.revisiontree import RevisionTree
44
48
from bzrlib.store.versioned import VersionedFileStore
45
49
from bzrlib.store.text import TextStore
46
50
from bzrlib.testament import Testament
47
 
 
 
51
from bzrlib.util import bencode
48
52
""")
49
53
 
50
54
from bzrlib.decorators import needs_read_lock, needs_write_lock
52
56
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
53
57
from bzrlib.symbol_versioning import (
54
58
        deprecated_method,
55
 
        zero_nine,
56
59
        )
57
 
from bzrlib.trace import mutter, note, warning
 
60
from bzrlib.trace import mutter, mutter_callsite, note, warning
58
61
 
59
62
 
60
63
# Old formats display a warning, but only once
61
64
_deprecation_warning_done = False
62
65
 
63
66
 
 
67
class CommitBuilder(object):
 
68
    """Provides an interface to build up a commit.
 
69
 
 
70
    This allows describing a tree to be committed without needing to 
 
71
    know the internals of the format of the repository.
 
72
    """
 
73
    
 
74
    # all clients should supply tree roots.
 
75
    record_root_entry = True
 
76
    # the default CommitBuilder does not manage trees whose root is versioned.
 
77
    _versioned_root = False
 
78
 
 
79
    def __init__(self, repository, parents, config, timestamp=None,
 
80
                 timezone=None, committer=None, revprops=None,
 
81
                 revision_id=None):
 
82
        """Initiate a CommitBuilder.
 
83
 
 
84
        :param repository: Repository to commit to.
 
85
        :param parents: Revision ids of the parents of the new revision.
 
86
        :param config: Configuration to use.
 
87
        :param timestamp: Optional timestamp recorded for commit.
 
88
        :param timezone: Optional timezone for timestamp.
 
89
        :param committer: Optional committer to set for commit.
 
90
        :param revprops: Optional dictionary of revision properties.
 
91
        :param revision_id: Optional revision id.
 
92
        """
 
93
        self._config = config
 
94
 
 
95
        if committer is None:
 
96
            self._committer = self._config.username()
 
97
        else:
 
98
            assert isinstance(committer, basestring), type(committer)
 
99
            self._committer = committer
 
100
 
 
101
        self.new_inventory = Inventory(None)
 
102
        self._new_revision_id = revision_id
 
103
        self.parents = parents
 
104
        self.repository = repository
 
105
 
 
106
        self._revprops = {}
 
107
        if revprops is not None:
 
108
            self._revprops.update(revprops)
 
109
 
 
110
        if timestamp is None:
 
111
            timestamp = time.time()
 
112
        # Restrict resolution to 1ms
 
113
        self._timestamp = round(timestamp, 3)
 
114
 
 
115
        if timezone is None:
 
116
            self._timezone = osutils.local_time_offset()
 
117
        else:
 
118
            self._timezone = int(timezone)
 
119
 
 
120
        self._generate_revision_if_needed()
 
121
        self.__heads = graph.HeadsCache(repository.get_graph()).heads
 
122
 
 
123
    def commit(self, message):
 
124
        """Make the actual commit.
 
125
 
 
126
        :return: The revision id of the recorded revision.
 
127
        """
 
128
        rev = _mod_revision.Revision(
 
129
                       timestamp=self._timestamp,
 
130
                       timezone=self._timezone,
 
131
                       committer=self._committer,
 
132
                       message=message,
 
133
                       inventory_sha1=self.inv_sha1,
 
134
                       revision_id=self._new_revision_id,
 
135
                       properties=self._revprops)
 
136
        rev.parent_ids = self.parents
 
137
        self.repository.add_revision(self._new_revision_id, rev,
 
138
            self.new_inventory, self._config)
 
139
        self.repository.commit_write_group()
 
140
        return self._new_revision_id
 
141
 
 
142
    def abort(self):
 
143
        """Abort the commit that is being built.
 
144
        """
 
145
        self.repository.abort_write_group()
 
146
 
 
147
    def revision_tree(self):
 
148
        """Return the tree that was just committed.
 
149
 
 
150
        After calling commit() this can be called to get a RevisionTree
 
151
        representing the newly committed tree. This is preferred to
 
152
        calling Repository.revision_tree() because that may require
 
153
        deserializing the inventory, while we already have a copy in
 
154
        memory.
 
155
        """
 
156
        return RevisionTree(self.repository, self.new_inventory,
 
157
                            self._new_revision_id)
 
158
 
 
159
    def finish_inventory(self):
 
160
        """Tell the builder that the inventory is finished."""
 
161
        if self.new_inventory.root is None:
 
162
            raise AssertionError('Root entry should be supplied to'
 
163
                ' record_entry_contents, as of bzr 0.10.',
 
164
                 DeprecationWarning, stacklevel=2)
 
165
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
166
        self.new_inventory.revision_id = self._new_revision_id
 
167
        self.inv_sha1 = self.repository.add_inventory(
 
168
            self._new_revision_id,
 
169
            self.new_inventory,
 
170
            self.parents
 
171
            )
 
172
 
 
173
    def _gen_revision_id(self):
 
174
        """Return new revision-id."""
 
175
        return generate_ids.gen_revision_id(self._config.username(),
 
176
                                            self._timestamp)
 
177
 
 
178
    def _generate_revision_if_needed(self):
 
179
        """Create a revision id if None was supplied.
 
180
        
 
181
        If the repository can not support user-specified revision ids
 
182
        they should override this function and raise CannotSetRevisionId
 
183
        if _new_revision_id is not None.
 
184
 
 
185
        :raises: CannotSetRevisionId
 
186
        """
 
187
        if self._new_revision_id is None:
 
188
            self._new_revision_id = self._gen_revision_id()
 
189
            self.random_revid = True
 
190
        else:
 
191
            self.random_revid = False
 
192
 
 
193
    def _heads(self, file_id, revision_ids):
 
194
        """Calculate the graph heads for revision_ids in the graph of file_id.
 
195
 
 
196
        This can use either a per-file graph or a global revision graph as we
 
197
        have an identity relationship between the two graphs.
 
198
        """
 
199
        return self.__heads(revision_ids)
 
200
 
 
201
    def _check_root(self, ie, parent_invs, tree):
 
202
        """Helper for record_entry_contents.
 
203
 
 
204
        :param ie: An entry being added.
 
205
        :param parent_invs: The inventories of the parent revisions of the
 
206
            commit.
 
207
        :param tree: The tree that is being committed.
 
208
        """
 
209
        # In this revision format, root entries have no knit or weave When
 
210
        # serializing out to disk and back in root.revision is always
 
211
        # _new_revision_id
 
212
        ie.revision = self._new_revision_id
 
213
 
 
214
    def _get_delta(self, ie, basis_inv, path):
 
215
        """Get a delta against the basis inventory for ie."""
 
216
        if ie.file_id not in basis_inv:
 
217
            # add
 
218
            return (None, path, ie.file_id, ie)
 
219
        elif ie != basis_inv[ie.file_id]:
 
220
            # common but altered
 
221
            # TODO: avoid tis id2path call.
 
222
            return (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
 
223
        else:
 
224
            # common, unaltered
 
225
            return None
 
226
 
 
227
    def record_entry_contents(self, ie, parent_invs, path, tree,
 
228
        content_summary):
 
229
        """Record the content of ie from tree into the commit if needed.
 
230
 
 
231
        Side effect: sets ie.revision when unchanged
 
232
 
 
233
        :param ie: An inventory entry present in the commit.
 
234
        :param parent_invs: The inventories of the parent revisions of the
 
235
            commit.
 
236
        :param path: The path the entry is at in the tree.
 
237
        :param tree: The tree which contains this entry and should be used to 
 
238
            obtain content.
 
239
        :param content_summary: Summary data from the tree about the paths
 
240
            content - stat, length, exec, sha/link target. This is only
 
241
            accessed when the entry has a revision of None - that is when it is
 
242
            a candidate to commit.
 
243
        :return: A tuple (change_delta, version_recorded). change_delta is 
 
244
            an inventory_delta change for this entry against the basis tree of
 
245
            the commit, or None if no change occured against the basis tree.
 
246
            version_recorded is True if a new version of the entry has been
 
247
            recorded. For instance, committing a merge where a file was only
 
248
            changed on the other side will return (delta, False).
 
249
        """
 
250
        if self.new_inventory.root is None:
 
251
            if ie.parent_id is not None:
 
252
                raise errors.RootMissing()
 
253
            self._check_root(ie, parent_invs, tree)
 
254
        if ie.revision is None:
 
255
            kind = content_summary[0]
 
256
        else:
 
257
            # ie is carried over from a prior commit
 
258
            kind = ie.kind
 
259
        # XXX: repository specific check for nested tree support goes here - if
 
260
        # the repo doesn't want nested trees we skip it ?
 
261
        if (kind == 'tree-reference' and
 
262
            not self.repository._format.supports_tree_reference):
 
263
            # mismatch between commit builder logic and repository:
 
264
            # this needs the entry creation pushed down into the builder.
 
265
            raise NotImplementedError('Missing repository subtree support.')
 
266
        self.new_inventory.add(ie)
 
267
 
 
268
        # TODO: slow, take it out of the inner loop.
 
269
        try:
 
270
            basis_inv = parent_invs[0]
 
271
        except IndexError:
 
272
            basis_inv = Inventory(root_id=None)
 
273
 
 
274
        # ie.revision is always None if the InventoryEntry is considered
 
275
        # for committing. We may record the previous parents revision if the
 
276
        # content is actually unchanged against a sole head.
 
277
        if ie.revision is not None:
 
278
            if not self._versioned_root and path == '':
 
279
                # repositories that do not version the root set the root's
 
280
                # revision to the new commit even when no change occurs, and
 
281
                # this masks when a change may have occurred against the basis,
 
282
                # so calculate if one happened.
 
283
                if ie.file_id in basis_inv:
 
284
                    delta = (basis_inv.id2path(ie.file_id), path,
 
285
                        ie.file_id, ie)
 
286
                else:
 
287
                    # add
 
288
                    delta = (None, path, ie.file_id, ie)
 
289
                return delta, False
 
290
            else:
 
291
                # we don't need to commit this, because the caller already
 
292
                # determined that an existing revision of this file is
 
293
                # appropriate.
 
294
                return None, (ie.revision == self._new_revision_id)
 
295
        # XXX: Friction: parent_candidates should return a list not a dict
 
296
        #      so that we don't have to walk the inventories again.
 
297
        parent_candiate_entries = ie.parent_candidates(parent_invs)
 
298
        head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
 
299
        heads = []
 
300
        for inv in parent_invs:
 
301
            if ie.file_id in inv:
 
302
                old_rev = inv[ie.file_id].revision
 
303
                if old_rev in head_set:
 
304
                    heads.append(inv[ie.file_id].revision)
 
305
                    head_set.remove(inv[ie.file_id].revision)
 
306
 
 
307
        store = False
 
308
        # now we check to see if we need to write a new record to the
 
309
        # file-graph.
 
310
        # We write a new entry unless there is one head to the ancestors, and
 
311
        # the kind-derived content is unchanged.
 
312
 
 
313
        # Cheapest check first: no ancestors, or more the one head in the
 
314
        # ancestors, we write a new node.
 
315
        if len(heads) != 1:
 
316
            store = True
 
317
        if not store:
 
318
            # There is a single head, look it up for comparison
 
319
            parent_entry = parent_candiate_entries[heads[0]]
 
320
            # if the non-content specific data has changed, we'll be writing a
 
321
            # node:
 
322
            if (parent_entry.parent_id != ie.parent_id or
 
323
                parent_entry.name != ie.name):
 
324
                store = True
 
325
        # now we need to do content specific checks:
 
326
        if not store:
 
327
            # if the kind changed the content obviously has
 
328
            if kind != parent_entry.kind:
 
329
                store = True
 
330
        if kind == 'file':
 
331
            assert content_summary[2] is not None, \
 
332
                "Files must not have executable = None"
 
333
            if not store:
 
334
                if (# if the file length changed we have to store:
 
335
                    parent_entry.text_size != content_summary[1] or
 
336
                    # if the exec bit has changed we have to store:
 
337
                    parent_entry.executable != content_summary[2]):
 
338
                    store = True
 
339
                elif parent_entry.text_sha1 == content_summary[3]:
 
340
                    # all meta and content is unchanged (using a hash cache
 
341
                    # hit to check the sha)
 
342
                    ie.revision = parent_entry.revision
 
343
                    ie.text_size = parent_entry.text_size
 
344
                    ie.text_sha1 = parent_entry.text_sha1
 
345
                    ie.executable = parent_entry.executable
 
346
                    return self._get_delta(ie, basis_inv, path), False
 
347
                else:
 
348
                    # Either there is only a hash change(no hash cache entry,
 
349
                    # or same size content change), or there is no change on
 
350
                    # this file at all.
 
351
                    # Provide the parent's hash to the store layer, so that the
 
352
                    # content is unchanged we will not store a new node.
 
353
                    nostore_sha = parent_entry.text_sha1
 
354
            if store:
 
355
                # We want to record a new node regardless of the presence or
 
356
                # absence of a content change in the file.
 
357
                nostore_sha = None
 
358
            ie.executable = content_summary[2]
 
359
            lines = tree.get_file(ie.file_id, path).readlines()
 
360
            try:
 
361
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
 
362
                    ie.file_id, lines, heads, nostore_sha)
 
363
            except errors.ExistingContent:
 
364
                # Turns out that the file content was unchanged, and we were
 
365
                # only going to store a new node if it was changed. Carry over
 
366
                # the entry.
 
367
                ie.revision = parent_entry.revision
 
368
                ie.text_size = parent_entry.text_size
 
369
                ie.text_sha1 = parent_entry.text_sha1
 
370
                ie.executable = parent_entry.executable
 
371
                return self._get_delta(ie, basis_inv, path), False
 
372
        elif kind == 'directory':
 
373
            if not store:
 
374
                # all data is meta here, nothing specific to directory, so
 
375
                # carry over:
 
376
                ie.revision = parent_entry.revision
 
377
                return self._get_delta(ie, basis_inv, path), False
 
378
            lines = []
 
379
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
380
        elif kind == 'symlink':
 
381
            current_link_target = content_summary[3]
 
382
            if not store:
 
383
                # symlink target is not generic metadata, check if it has
 
384
                # changed.
 
385
                if current_link_target != parent_entry.symlink_target:
 
386
                    store = True
 
387
            if not store:
 
388
                # unchanged, carry over.
 
389
                ie.revision = parent_entry.revision
 
390
                ie.symlink_target = parent_entry.symlink_target
 
391
                return self._get_delta(ie, basis_inv, path), False
 
392
            ie.symlink_target = current_link_target
 
393
            lines = []
 
394
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
395
        elif kind == 'tree-reference':
 
396
            if not store:
 
397
                if content_summary[3] != parent_entry.reference_revision:
 
398
                    store = True
 
399
            if not store:
 
400
                # unchanged, carry over.
 
401
                ie.reference_revision = parent_entry.reference_revision
 
402
                ie.revision = parent_entry.revision
 
403
                return self._get_delta(ie, basis_inv, path), False
 
404
            ie.reference_revision = content_summary[3]
 
405
            lines = []
 
406
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
407
        else:
 
408
            raise NotImplementedError('unknown kind')
 
409
        ie.revision = self._new_revision_id
 
410
        return self._get_delta(ie, basis_inv, path), True
 
411
 
 
412
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
413
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
414
            file_id, self.repository.get_transaction())
 
415
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
 
416
        # than add_lines, and allows committing when a parent is ghosted for
 
417
        # some reason.
 
418
        # Note: as we read the content directly from the tree, we know its not
 
419
        # been turned into unicode or badly split - but a broken tree
 
420
        # implementation could give us bad output from readlines() so this is
 
421
        # not a guarantee of safety. What would be better is always checking
 
422
        # the content during test suite execution. RBC 20070912
 
423
        try:
 
424
            return versionedfile.add_lines_with_ghosts(
 
425
                self._new_revision_id, parents, new_lines,
 
426
                nostore_sha=nostore_sha, random_id=self.random_revid,
 
427
                check_content=False)[0:2]
 
428
        finally:
 
429
            versionedfile.clear_cache()
 
430
 
 
431
 
 
432
class RootCommitBuilder(CommitBuilder):
 
433
    """This commitbuilder actually records the root id"""
 
434
    
 
435
    # the root entry gets versioned properly by this builder.
 
436
    _versioned_root = True
 
437
 
 
438
    def _check_root(self, ie, parent_invs, tree):
 
439
        """Helper for record_entry_contents.
 
440
 
 
441
        :param ie: An entry being added.
 
442
        :param parent_invs: The inventories of the parent revisions of the
 
443
            commit.
 
444
        :param tree: The tree that is being committed.
 
445
        """
 
446
 
 
447
 
64
448
######################################################################
65
449
# Repositories
66
450
 
76
460
    remote) disk.
77
461
    """
78
462
 
 
463
    # What class to use for a CommitBuilder. Often its simpler to change this
 
464
    # in a Repository class subclass rather than to override
 
465
    # get_commit_builder.
 
466
    _commit_builder_class = CommitBuilder
 
467
    # The search regex used by xml based repositories to determine what things
 
468
    # where changed in a single commit.
79
469
    _file_ids_altered_regex = lazy_regex.lazy_compile(
80
470
        r'file_id="(?P<file_id>[^"]+)"'
81
 
        r'.*revision="(?P<revision_id>[^"]+)"'
 
471
        r'.* revision="(?P<revision_id>[^"]+)"'
82
472
        )
83
473
 
 
474
    def abort_write_group(self):
 
475
        """Commit the contents accrued within the current write group.
 
476
 
 
477
        :seealso: start_write_group.
 
478
        """
 
479
        if self._write_group is not self.get_transaction():
 
480
            # has an unlock or relock occured ?
 
481
            raise errors.BzrError('mismatched lock context and write group.')
 
482
        self._abort_write_group()
 
483
        self._write_group = None
 
484
 
 
485
    def _abort_write_group(self):
 
486
        """Template method for per-repository write group cleanup.
 
487
        
 
488
        This is called during abort before the write group is considered to be 
 
489
        finished and should cleanup any internal state accrued during the write
 
490
        group. There is no requirement that data handed to the repository be
 
491
        *not* made available - this is not a rollback - but neither should any
 
492
        attempt be made to ensure that data added is fully commited. Abort is
 
493
        invoked when an error has occured so futher disk or network operations
 
494
        may not be possible or may error and if possible should not be
 
495
        attempted.
 
496
        """
 
497
 
84
498
    @needs_write_lock
85
499
    def add_inventory(self, revision_id, inv, parents):
86
500
        """Add the inventory inv to the repository as revision_id.
90
504
 
91
505
        returns the sha1 of the serialized inventory.
92
506
        """
93
 
        revision_id = osutils.safe_revision_id(revision_id)
 
507
        assert self.is_in_write_group()
94
508
        _mod_revision.check_not_reserved_id(revision_id)
95
509
        assert inv.revision_id is None or inv.revision_id == revision_id, \
96
510
            "Mismatch between inventory revision" \
97
511
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
98
512
        assert inv.root is not None
99
 
        inv_text = self.serialise_inventory(inv)
100
 
        inv_sha1 = osutils.sha_string(inv_text)
101
 
        inv_vf = self.control_weaves.get_weave('inventory',
102
 
                                               self.get_transaction())
103
 
        self._inventory_add_lines(inv_vf, revision_id, parents,
104
 
                                  osutils.split_lines(inv_text))
105
 
        return inv_sha1
 
513
        inv_lines = self._serialise_inventory_to_lines(inv)
 
514
        inv_vf = self.get_inventory_weave()
 
515
        return self._inventory_add_lines(inv_vf, revision_id, parents,
 
516
            inv_lines, check_content=False)
106
517
 
107
 
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
 
518
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
 
519
        check_content=True):
 
520
        """Store lines in inv_vf and return the sha1 of the inventory."""
108
521
        final_parents = []
109
522
        for parent in parents:
110
523
            if parent in inv_vf:
111
524
                final_parents.append(parent)
112
 
 
113
 
        inv_vf.add_lines(revision_id, final_parents, lines)
 
525
        return inv_vf.add_lines(revision_id, final_parents, lines,
 
526
            check_content=check_content)[0]
114
527
 
115
528
    @needs_write_lock
116
529
    def add_revision(self, revision_id, rev, inv=None, config=None):
124
537
                       If supplied its signature_needed method will be used
125
538
                       to determine if a signature should be made.
126
539
        """
127
 
        revision_id = osutils.safe_revision_id(revision_id)
128
540
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
129
541
        #       rev.parent_ids?
130
542
        _mod_revision.check_not_reserved_id(revision_id)
143
555
                self.add_inventory(revision_id, inv, rev.parent_ids)
144
556
        self._revision_store.add_revision(rev, self.get_transaction())
145
557
 
146
 
    @needs_read_lock
147
 
    def _all_possible_ids(self):
148
 
        """Return all the possible revisions that we could find."""
149
 
        return self.get_inventory_weave().versions()
 
558
    def _add_revision_text(self, revision_id, text):
 
559
        revision = self._revision_store._serializer.read_revision_from_string(
 
560
            text)
 
561
        self._revision_store._add_revision(revision, StringIO(text),
 
562
                                           self.get_transaction())
150
563
 
151
564
    def all_revision_ids(self):
152
565
        """Returns a list of all the revision ids in the repository. 
155
568
        reachable from a particular revision, and ignore any other revisions
156
569
        that might be present.  There is no direct replacement method.
157
570
        """
 
571
        if 'evil' in debug.debug_flags:
 
572
            mutter_callsite(2, "all_revision_ids is linear with history.")
158
573
        return self._all_revision_ids()
159
574
 
160
 
    @needs_read_lock
161
575
    def _all_revision_ids(self):
162
576
        """Returns a list of all the revision ids in the repository. 
163
577
 
164
578
        These are in as much topological order as the underlying store can 
165
 
        present: for weaves ghosts may lead to a lack of correctness until
166
 
        the reweave updates the parents list.
 
579
        present.
167
580
        """
168
 
        if self._revision_store.text_store.listable():
169
 
            return self._revision_store.all_revision_ids(self.get_transaction())
170
 
        result = self._all_possible_ids()
171
 
        # TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
172
 
        #       ids. (It should, since _revision_store's API should change to
173
 
        #       return utf8 revision_ids)
174
 
        return self._eliminate_revisions_not_present(result)
 
581
        raise NotImplementedError(self._all_revision_ids)
175
582
 
176
583
    def break_lock(self):
177
584
        """Break a lock if one is present from another instance.
214
621
        self.bzrdir = a_bzrdir
215
622
        self.control_files = control_files
216
623
        self._revision_store = _revision_store
217
 
        self.text_store = text_store
218
624
        # backwards compatibility
219
625
        self.weave_store = text_store
 
626
        # for tests
 
627
        self._reconcile_does_inventory_gc = True
 
628
        self._reconcile_fixes_text_parents = False
 
629
        self._reconcile_backsup_inventory = True
220
630
        # not right yet - should be more semantically clear ? 
221
631
        # 
222
632
        self.control_store = control_store
224
634
        # TODO: make sure to construct the right store classes, etc, depending
225
635
        # on whether escaping is required.
226
636
        self._warn_if_deprecated()
 
637
        self._write_group = None
 
638
        self.base = control_files._transport.base
227
639
 
228
640
    def __repr__(self):
229
 
        return '%s(%r)' % (self.__class__.__name__, 
230
 
                           self.bzrdir.transport.base)
 
641
        return '%s(%r)' % (self.__class__.__name__,
 
642
                           self.base)
 
643
 
 
644
    def has_same_location(self, other):
 
645
        """Returns a boolean indicating if this repository is at the same
 
646
        location as another repository.
 
647
 
 
648
        This might return False even when two repository objects are accessing
 
649
        the same physical repository via different URLs.
 
650
        """
 
651
        if self.__class__ is not other.__class__:
 
652
            return False
 
653
        return (self.control_files._transport.base ==
 
654
                other.control_files._transport.base)
 
655
 
 
656
    def is_in_write_group(self):
 
657
        """Return True if there is an open write group.
 
658
 
 
659
        :seealso: start_write_group.
 
660
        """
 
661
        return self._write_group is not None
231
662
 
232
663
    def is_locked(self):
233
664
        return self.control_files.is_locked()
234
665
 
 
666
    def is_write_locked(self):
 
667
        """Return True if this object is write locked."""
 
668
        return self.is_locked() and self.control_files._lock_mode == 'w'
 
669
 
235
670
    def lock_write(self, token=None):
236
671
        """Lock this repository for writing.
 
672
 
 
673
        This causes caching within the repository obejct to start accumlating
 
674
        data during reads, and allows a 'write_group' to be obtained. Write
 
675
        groups must be used for actual data insertion.
237
676
        
238
677
        :param token: if this is already locked, then lock_write will fail
239
678
            unless the token matches the existing lock.
242
681
            instance doesn't support using token locks.
243
682
        :raises MismatchedToken: if the specified token doesn't match the token
244
683
            of the existing lock.
 
684
        :seealso: start_write_group.
245
685
 
246
686
        A token should be passed in if you know that you have locked the object
247
687
        some other way, and need to synchronise this object's state with that
249
689
 
250
690
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
251
691
        """
252
 
        return self.control_files.lock_write(token=token)
 
692
        result = self.control_files.lock_write(token=token)
 
693
        self._refresh_data()
 
694
        return result
253
695
 
254
696
    def lock_read(self):
255
697
        self.control_files.lock_read()
 
698
        self._refresh_data()
256
699
 
257
700
    def get_physical_lock_status(self):
258
701
        return self.control_files.get_physical_lock_status()
323
766
            result['size'] = t
324
767
        return result
325
768
 
 
769
    def find_branches(self, using=False):
 
770
        """Find branches underneath this repository.
 
771
 
 
772
        This will include branches inside other branches.
 
773
 
 
774
        :param using: If True, list only branches using this repository.
 
775
        """
 
776
        if using and not self.is_shared():
 
777
            try:
 
778
                return [self.bzrdir.open_branch()]
 
779
            except errors.NotBranchError:
 
780
                return []
 
781
        class Evaluator(object):
 
782
 
 
783
            def __init__(self):
 
784
                self.first_call = True
 
785
 
 
786
            def __call__(self, bzrdir):
 
787
                # On the first call, the parameter is always the bzrdir
 
788
                # containing the current repo.
 
789
                if not self.first_call:
 
790
                    try:
 
791
                        repository = bzrdir.open_repository()
 
792
                    except errors.NoRepositoryPresent:
 
793
                        pass
 
794
                    else:
 
795
                        return False, (None, repository)
 
796
                self.first_call = False
 
797
                try:
 
798
                    value = (bzrdir.open_branch(), None)
 
799
                except errors.NotBranchError:
 
800
                    value = (None, None)
 
801
                return True, value
 
802
 
 
803
        branches = []
 
804
        for branch, repository in bzrdir.BzrDir.find_bzrdirs(
 
805
                self.bzrdir.root_transport, evaluate=Evaluator()):
 
806
            if branch is not None:
 
807
                branches.append(branch)
 
808
            if not using and repository is not None:
 
809
                branches.extend(repository.find_branches())
 
810
        return branches
 
811
 
 
812
    def get_data_stream(self, revision_ids):
 
813
        raise NotImplementedError(self.get_data_stream)
 
814
 
 
815
    def insert_data_stream(self, stream):
 
816
        """XXX What does this really do? 
 
817
        
 
818
        Is it a substitute for fetch? 
 
819
        Should it manage its own write group ?
 
820
        """
 
821
        for item_key, bytes in stream:
 
822
            if item_key[0] == 'file':
 
823
                (file_id,) = item_key[1:]
 
824
                knit = self.weave_store.get_weave_or_empty(
 
825
                    file_id, self.get_transaction())
 
826
            elif item_key == ('inventory',):
 
827
                knit = self.get_inventory_weave()
 
828
            elif item_key == ('revisions',):
 
829
                knit = self._revision_store.get_revision_file(
 
830
                    self.get_transaction())
 
831
            elif item_key == ('signatures',):
 
832
                knit = self._revision_store.get_signature_file(
 
833
                    self.get_transaction())
 
834
            else:
 
835
                raise RepositoryDataStreamError(
 
836
                    "Unrecognised data stream key '%s'" % (item_key,))
 
837
            decoded_list = bencode.bdecode(bytes)
 
838
            format = decoded_list.pop(0)
 
839
            data_list = []
 
840
            knit_bytes = ''
 
841
            for version, options, parents, some_bytes in decoded_list:
 
842
                data_list.append((version, options, len(some_bytes), parents))
 
843
                knit_bytes += some_bytes
 
844
            knit.insert_data_stream(
 
845
                (format, data_list, StringIO(knit_bytes).read))
 
846
 
326
847
    @needs_read_lock
327
 
    def missing_revision_ids(self, other, revision_id=None):
 
848
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
328
849
        """Return the revision ids that other has that this does not.
329
850
        
330
851
        These are returned in topological order.
331
852
 
332
853
        revision_id: only return revision ids included by revision_id.
333
854
        """
334
 
        revision_id = osutils.safe_revision_id(revision_id)
335
 
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
 
855
        return InterRepository.get(other, self).missing_revision_ids(
 
856
            revision_id, find_ghosts)
336
857
 
337
858
    @staticmethod
338
859
    def open(base):
350
871
        This is a destructive operation! Do not use it on existing 
351
872
        repositories.
352
873
        """
353
 
        revision_id = osutils.safe_revision_id(revision_id)
354
874
        return InterRepository.get(self, destination).copy_content(revision_id)
355
875
 
356
 
    def fetch(self, source, revision_id=None, pb=None):
 
876
    def commit_write_group(self):
 
877
        """Commit the contents accrued within the current write group.
 
878
 
 
879
        :seealso: start_write_group.
 
880
        """
 
881
        if self._write_group is not self.get_transaction():
 
882
            # has an unlock or relock occured ?
 
883
            raise errors.BzrError('mismatched lock context %r and '
 
884
                'write group %r.' %
 
885
                (self.get_transaction(), self._write_group))
 
886
        self._commit_write_group()
 
887
        self._write_group = None
 
888
 
 
889
    def _commit_write_group(self):
 
890
        """Template method for per-repository write group cleanup.
 
891
        
 
892
        This is called before the write group is considered to be 
 
893
        finished and should ensure that all data handed to the repository
 
894
        for writing during the write group is safely committed (to the 
 
895
        extent possible considering file system caching etc).
 
896
        """
 
897
 
 
898
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
357
899
        """Fetch the content required to construct revision_id from source.
358
900
 
359
901
        If revision_id is None all content is copied.
 
902
        :param find_ghosts: Find and copy revisions in the source that are
 
903
            ghosts in the target (and not reachable directly by walking out to
 
904
            the first-present revision in target from revision_id).
360
905
        """
361
 
        revision_id = osutils.safe_revision_id(revision_id)
 
906
        # fast path same-url fetch operations
 
907
        if self.has_same_location(source):
 
908
            # check that last_revision is in 'from' and then return a
 
909
            # no-operation.
 
910
            if (revision_id is not None and
 
911
                not _mod_revision.is_null(revision_id)):
 
912
                self.get_revision(revision_id)
 
913
            return 0, []
362
914
        inter = InterRepository.get(source, self)
363
915
        try:
364
 
            return inter.fetch(revision_id=revision_id, pb=pb)
 
916
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
365
917
        except NotImplementedError:
366
918
            raise errors.IncompatibleRepositories(source, self)
367
919
 
368
 
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
369
 
                           timezone=None, committer=None, revprops=None, 
 
920
    def create_bundle(self, target, base, fileobj, format=None):
 
921
        return serializer.write_bundle(self, target, base, fileobj, format)
 
922
 
 
923
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
924
                           timezone=None, committer=None, revprops=None,
370
925
                           revision_id=None):
371
926
        """Obtain a CommitBuilder for this repository.
372
927
        
379
934
        :param revprops: Optional dictionary of revision properties.
380
935
        :param revision_id: Optional revision id.
381
936
        """
382
 
        revision_id = osutils.safe_revision_id(revision_id)
383
 
        return _CommitBuilder(self, parents, config, timestamp, timezone,
384
 
                              committer, revprops, revision_id)
 
937
        result = self._commit_builder_class(self, parents, config,
 
938
            timestamp, timezone, committer, revprops, revision_id)
 
939
        self.start_write_group()
 
940
        return result
385
941
 
386
942
    def unlock(self):
 
943
        if (self.control_files._lock_count == 1 and
 
944
            self.control_files._lock_mode == 'w'):
 
945
            if self._write_group is not None:
 
946
                self.abort_write_group()
 
947
                self.control_files.unlock()
 
948
                raise errors.BzrError(
 
949
                    'Must end write groups before releasing write locks.')
387
950
        self.control_files.unlock()
388
951
 
389
952
    @needs_read_lock
401
964
        self.copy_content_into(dest_repo, revision_id)
402
965
        return dest_repo
403
966
 
 
967
    def start_write_group(self):
 
968
        """Start a write group in the repository.
 
969
 
 
970
        Write groups are used by repositories which do not have a 1:1 mapping
 
971
        between file ids and backend store to manage the insertion of data from
 
972
        both fetch and commit operations.
 
973
 
 
974
        A write lock is required around the start_write_group/commit_write_group
 
975
        for the support of lock-requiring repository formats.
 
976
 
 
977
        One can only insert data into a repository inside a write group.
 
978
 
 
979
        :return: None.
 
980
        """
 
981
        if not self.is_write_locked():
 
982
            raise errors.NotWriteLocked(self)
 
983
        if self._write_group:
 
984
            raise errors.BzrError('already in a write group')
 
985
        self._start_write_group()
 
986
        # so we can detect unlock/relock - the write group is now entered.
 
987
        self._write_group = self.get_transaction()
 
988
 
 
989
    def _start_write_group(self):
 
990
        """Template method for per-repository write group startup.
 
991
        
 
992
        This is called before the write group is considered to be 
 
993
        entered.
 
994
        """
 
995
 
404
996
    @needs_read_lock
405
997
    def sprout(self, to_bzrdir, revision_id=None):
406
998
        """Create a descendent repository for new development.
427
1019
    @needs_read_lock
428
1020
    def has_revision(self, revision_id):
429
1021
        """True if this repository has a copy of the revision."""
430
 
        revision_id = osutils.safe_revision_id(revision_id)
 
1022
        if 'evil' in debug.debug_flags:
 
1023
            mutter_callsite(3, "has_revision is a LBYL symptom.")
431
1024
        return self._revision_store.has_revision_id(revision_id,
432
1025
                                                    self.get_transaction())
433
1026
 
434
1027
    @needs_read_lock
 
1028
    def get_revision(self, revision_id):
 
1029
        """Return the Revision object for a named revision."""
 
1030
        return self.get_revisions([revision_id])[0]
 
1031
 
 
1032
    @needs_read_lock
435
1033
    def get_revision_reconcile(self, revision_id):
436
1034
        """'reconcile' helper routine that allows access to a revision always.
437
1035
        
440
1038
        be used by reconcile, or reconcile-alike commands that are correcting
441
1039
        or testing the revision graph.
442
1040
        """
443
 
        if not revision_id or not isinstance(revision_id, basestring):
444
 
            raise errors.InvalidRevisionId(revision_id=revision_id,
445
 
                                           branch=self)
446
 
        return self.get_revisions([revision_id])[0]
 
1041
        return self._get_revisions([revision_id])[0]
447
1042
 
448
1043
    @needs_read_lock
449
1044
    def get_revisions(self, revision_ids):
450
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
 
1045
        """Get many revisions at once."""
 
1046
        return self._get_revisions(revision_ids)
 
1047
 
 
1048
    @needs_read_lock
 
1049
    def _get_revisions(self, revision_ids):
 
1050
        """Core work logic to get many revisions without sanity checks."""
 
1051
        for rev_id in revision_ids:
 
1052
            if not rev_id or not isinstance(rev_id, basestring):
 
1053
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
451
1054
        revs = self._revision_store.get_revisions(revision_ids,
452
1055
                                                  self.get_transaction())
453
1056
        for rev in revs:
461
1064
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
462
1065
        #       would have already do it.
463
1066
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
464
 
        revision_id = osutils.safe_revision_id(revision_id)
465
1067
        rev = self.get_revision(revision_id)
466
1068
        rev_tmp = StringIO()
467
1069
        # the current serializer..
470
1072
        return rev_tmp.getvalue()
471
1073
 
472
1074
    @needs_read_lock
473
 
    def get_revision(self, revision_id):
474
 
        """Return the Revision object for a named revision"""
475
 
        # TODO: jam 20070210 get_revision_reconcile should do this for us
476
 
        revision_id = osutils.safe_revision_id(revision_id)
477
 
        r = self.get_revision_reconcile(revision_id)
478
 
        # weave corruption can lead to absent revision markers that should be
479
 
        # present.
480
 
        # the following test is reasonably cheap (it needs a single weave read)
481
 
        # and the weave is cached in read transactions. In write transactions
482
 
        # it is not cached but typically we only read a small number of
483
 
        # revisions. For knits when they are introduced we will probably want
484
 
        # to ensure that caching write transactions are in use.
485
 
        inv = self.get_inventory_weave()
486
 
        self._check_revision_parents(r, inv)
487
 
        return r
488
 
 
489
 
    @needs_read_lock
490
1075
    def get_deltas_for_revisions(self, revisions):
491
1076
        """Produce a generator of revision deltas.
492
1077
        
517
1102
        r = self.get_revision(revision_id)
518
1103
        return list(self.get_deltas_for_revisions([r]))[0]
519
1104
 
520
 
    def _check_revision_parents(self, revision, inventory):
521
 
        """Private to Repository and Fetch.
522
 
        
523
 
        This checks the parentage of revision in an inventory weave for 
524
 
        consistency and is only applicable to inventory-weave-for-ancestry
525
 
        using repository formats & fetchers.
526
 
        """
527
 
        weave_parents = inventory.get_parents(revision.revision_id)
528
 
        weave_names = inventory.versions()
529
 
        for parent_id in revision.parent_ids:
530
 
            if parent_id in weave_names:
531
 
                # this parent must not be a ghost.
532
 
                if not parent_id in weave_parents:
533
 
                    # but it is a ghost
534
 
                    raise errors.CorruptRepository(self)
535
 
 
536
1105
    @needs_write_lock
537
1106
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
538
 
        revision_id = osutils.safe_revision_id(revision_id)
539
1107
        signature = gpg_strategy.sign(plaintext)
 
1108
        self.add_signature_text(revision_id, signature)
 
1109
 
 
1110
    @needs_write_lock
 
1111
    def add_signature_text(self, revision_id, signature):
540
1112
        self._revision_store.add_revision_signature_text(revision_id,
541
1113
                                                         signature,
542
1114
                                                         self.get_transaction())
543
1115
 
544
 
    def fileids_altered_by_revision_ids(self, revision_ids):
545
 
        """Find the file ids and versions affected by revisions.
 
1116
    def find_text_key_references(self):
 
1117
        """Find the text key references within the repository.
546
1118
 
547
 
        :param revisions: an iterable containing revision ids.
548
 
        :return: a dictionary mapping altered file-ids to an iterable of
 
1119
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
549
1120
        revision_ids. Each altered file-ids has the exact revision_ids that
550
1121
        altered it listed explicitly.
 
1122
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
1123
            to whether they were referred to by the inventory of the
 
1124
            revision_id that they contain. The inventory texts from all present
 
1125
            revision ids are assessed to generate this report.
551
1126
        """
552
 
        assert self._serializer.support_altered_by_hack, \
553
 
            ("fileids_altered_by_revision_ids only supported for branches " 
554
 
             "which store inventory as unnested xml, not on %r" % self)
555
 
        selected_revision_ids = set(osutils.safe_revision_id(r)
556
 
                                    for r in revision_ids)
 
1127
        revision_ids = self.all_revision_ids()
557
1128
        w = self.get_inventory_weave()
 
1129
        pb = ui.ui_factory.nested_progress_bar()
 
1130
        try:
 
1131
            return self._find_text_key_references_from_xml_inventory_lines(
 
1132
                w.iter_lines_added_or_present_in_versions(revision_ids, pb=pb))
 
1133
        finally:
 
1134
            pb.finished()
 
1135
 
 
1136
    def _find_text_key_references_from_xml_inventory_lines(self,
 
1137
        line_iterator):
 
1138
        """Core routine for extracting references to texts from inventories.
 
1139
 
 
1140
        This performs the translation of xml lines to revision ids.
 
1141
 
 
1142
        :param line_iterator: An iterator of lines, origin_version_id
 
1143
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
1144
            to whether they were referred to by the inventory of the
 
1145
            revision_id that they contain. Note that if that revision_id was
 
1146
            not part of the line_iterator's output then False will be given -
 
1147
            even though it may actually refer to that key.
 
1148
        """
 
1149
        if not self._serializer.support_altered_by_hack:
 
1150
            raise AssertionError(
 
1151
                "_find_text_key_references_from_xml_inventory_lines only "
 
1152
                "supported for branches which store inventory as unnested xml"
 
1153
                ", not on %r" % self)
558
1154
        result = {}
559
1155
 
560
1156
        # this code needs to read every new line in every inventory for the
576
1172
        search = self._file_ids_altered_regex.search
577
1173
        unescape = _unescape_xml
578
1174
        setdefault = result.setdefault
579
 
        pb = ui.ui_factory.nested_progress_bar()
580
 
        try:
581
 
            for line in w.iter_lines_added_or_present_in_versions(
582
 
                                        selected_revision_ids, pb=pb):
583
 
                match = search(line)
584
 
                if match is None:
585
 
                    continue
586
 
                # One call to match.group() returning multiple items is quite a
587
 
                # bit faster than 2 calls to match.group() each returning 1
588
 
                file_id, revision_id = match.group('file_id', 'revision_id')
589
 
 
590
 
                # Inlining the cache lookups helps a lot when you make 170,000
591
 
                # lines and 350k ids, versus 8.4 unique ids.
592
 
                # Using a cache helps in 2 ways:
593
 
                #   1) Avoids unnecessary decoding calls
594
 
                #   2) Re-uses cached strings, which helps in future set and
595
 
                #      equality checks.
596
 
                # (2) is enough that removing encoding entirely along with
597
 
                # the cache (so we are using plain strings) results in no
598
 
                # performance improvement.
599
 
                try:
600
 
                    revision_id = unescape_revid_cache[revision_id]
601
 
                except KeyError:
602
 
                    unescaped = unescape(revision_id)
603
 
                    unescape_revid_cache[revision_id] = unescaped
604
 
                    revision_id = unescaped
605
 
 
606
 
                if revision_id in selected_revision_ids:
607
 
                    try:
608
 
                        file_id = unescape_fileid_cache[file_id]
609
 
                    except KeyError:
610
 
                        unescaped = unescape(file_id)
611
 
                        unescape_fileid_cache[file_id] = unescaped
612
 
                        file_id = unescaped
613
 
                    setdefault(file_id, set()).add(revision_id)
614
 
        finally:
615
 
            pb.finished()
616
 
        return result
 
1175
        for line, version_id in line_iterator:
 
1176
            match = search(line)
 
1177
            if match is None:
 
1178
                continue
 
1179
            # One call to match.group() returning multiple items is quite a
 
1180
            # bit faster than 2 calls to match.group() each returning 1
 
1181
            file_id, revision_id = match.group('file_id', 'revision_id')
 
1182
 
 
1183
            # Inlining the cache lookups helps a lot when you make 170,000
 
1184
            # lines and 350k ids, versus 8.4 unique ids.
 
1185
            # Using a cache helps in 2 ways:
 
1186
            #   1) Avoids unnecessary decoding calls
 
1187
            #   2) Re-uses cached strings, which helps in future set and
 
1188
            #      equality checks.
 
1189
            # (2) is enough that removing encoding entirely along with
 
1190
            # the cache (so we are using plain strings) results in no
 
1191
            # performance improvement.
 
1192
            try:
 
1193
                revision_id = unescape_revid_cache[revision_id]
 
1194
            except KeyError:
 
1195
                unescaped = unescape(revision_id)
 
1196
                unescape_revid_cache[revision_id] = unescaped
 
1197
                revision_id = unescaped
 
1198
 
 
1199
            # Note that unconditionally unescaping means that we deserialise
 
1200
            # every fileid, which for general 'pull' is not great, but we don't
 
1201
            # really want to have some many fulltexts that this matters anyway.
 
1202
            # RBC 20071114.
 
1203
            try:
 
1204
                file_id = unescape_fileid_cache[file_id]
 
1205
            except KeyError:
 
1206
                unescaped = unescape(file_id)
 
1207
                unescape_fileid_cache[file_id] = unescaped
 
1208
                file_id = unescaped
 
1209
 
 
1210
            key = (file_id, revision_id)
 
1211
            setdefault(key, False)
 
1212
            if revision_id == version_id:
 
1213
                result[key] = True
 
1214
        return result
 
1215
 
 
1216
    def _find_file_ids_from_xml_inventory_lines(self, line_iterator,
 
1217
        revision_ids):
 
1218
        """Helper routine for fileids_altered_by_revision_ids.
 
1219
 
 
1220
        This performs the translation of xml lines to revision ids.
 
1221
 
 
1222
        :param line_iterator: An iterator of lines, origin_version_id
 
1223
        :param revision_ids: The revision ids to filter for. This should be a
 
1224
            set or other type which supports efficient __contains__ lookups, as
 
1225
            the revision id from each parsed line will be looked up in the
 
1226
            revision_ids filter.
 
1227
        :return: a dictionary mapping altered file-ids to an iterable of
 
1228
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1229
        altered it listed explicitly.
 
1230
        """
 
1231
        result = {}
 
1232
        setdefault = result.setdefault
 
1233
        for file_id, revision_id in \
 
1234
            self._find_text_key_references_from_xml_inventory_lines(
 
1235
                line_iterator).iterkeys():
 
1236
            # once data is all ensured-consistent; then this is
 
1237
            # if revision_id == version_id
 
1238
            if revision_id in revision_ids:
 
1239
                setdefault(file_id, set()).add(revision_id)
 
1240
        return result
 
1241
 
 
1242
    def fileids_altered_by_revision_ids(self, revision_ids):
 
1243
        """Find the file ids and versions affected by revisions.
 
1244
 
 
1245
        :param revisions: an iterable containing revision ids.
 
1246
        :return: a dictionary mapping altered file-ids to an iterable of
 
1247
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1248
        altered it listed explicitly.
 
1249
        """
 
1250
        selected_revision_ids = set(revision_ids)
 
1251
        w = self.get_inventory_weave()
 
1252
        pb = ui.ui_factory.nested_progress_bar()
 
1253
        try:
 
1254
            return self._find_file_ids_from_xml_inventory_lines(
 
1255
                w.iter_lines_added_or_present_in_versions(
 
1256
                    selected_revision_ids, pb=pb),
 
1257
                selected_revision_ids)
 
1258
        finally:
 
1259
            pb.finished()
 
1260
 
 
1261
    def iter_files_bytes(self, desired_files):
 
1262
        """Iterate through file versions.
 
1263
 
 
1264
        Files will not necessarily be returned in the order they occur in
 
1265
        desired_files.  No specific order is guaranteed.
 
1266
 
 
1267
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
 
1268
        value supplied by the caller as part of desired_files.  It should
 
1269
        uniquely identify the file version in the caller's context.  (Examples:
 
1270
        an index number or a TreeTransform trans_id.)
 
1271
 
 
1272
        bytes_iterator is an iterable of bytestrings for the file.  The
 
1273
        kind of iterable and length of the bytestrings are unspecified, but for
 
1274
        this implementation, it is a list of lines produced by
 
1275
        VersionedFile.get_lines().
 
1276
 
 
1277
        :param desired_files: a list of (file_id, revision_id, identifier)
 
1278
            triples
 
1279
        """
 
1280
        transaction = self.get_transaction()
 
1281
        for file_id, revision_id, callable_data in desired_files:
 
1282
            try:
 
1283
                weave = self.weave_store.get_weave(file_id, transaction)
 
1284
            except errors.NoSuchFile:
 
1285
                raise errors.NoSuchIdInRepository(self, file_id)
 
1286
            yield callable_data, weave.get_lines(revision_id)
 
1287
 
 
1288
    def _generate_text_key_index(self, text_key_references=None,
 
1289
        ancestors=None):
 
1290
        """Generate a new text key index for the repository.
 
1291
 
 
1292
        This is an expensive function that will take considerable time to run.
 
1293
 
 
1294
        :return: A dict mapping text keys ((file_id, revision_id) tuples) to a
 
1295
            list of parents, also text keys. When a given key has no parents,
 
1296
            the parents list will be [NULL_REVISION].
 
1297
        """
 
1298
        # All revisions, to find inventory parents.
 
1299
        if ancestors is None:
 
1300
            revision_graph = self.get_revision_graph_with_ghosts()
 
1301
            ancestors = revision_graph.get_ancestors()
 
1302
        if text_key_references is None:
 
1303
            text_key_references = self.find_text_key_references()
 
1304
        pb = ui.ui_factory.nested_progress_bar()
 
1305
        try:
 
1306
            return self._do_generate_text_key_index(ancestors,
 
1307
                text_key_references, pb)
 
1308
        finally:
 
1309
            pb.finished()
 
1310
 
 
1311
    def _do_generate_text_key_index(self, ancestors, text_key_references, pb):
 
1312
        """Helper for _generate_text_key_index to avoid deep nesting."""
 
1313
        revision_order = tsort.topo_sort(ancestors)
 
1314
        invalid_keys = set()
 
1315
        revision_keys = {}
 
1316
        for revision_id in revision_order:
 
1317
            revision_keys[revision_id] = set()
 
1318
        text_count = len(text_key_references)
 
1319
        # a cache of the text keys to allow reuse; costs a dict of all the
 
1320
        # keys, but saves a 2-tuple for every child of a given key.
 
1321
        text_key_cache = {}
 
1322
        for text_key, valid in text_key_references.iteritems():
 
1323
            if not valid:
 
1324
                invalid_keys.add(text_key)
 
1325
            else:
 
1326
                revision_keys[text_key[1]].add(text_key)
 
1327
            text_key_cache[text_key] = text_key
 
1328
        del text_key_references
 
1329
        text_index = {}
 
1330
        text_graph = graph.Graph(graph.DictParentsProvider(text_index))
 
1331
        NULL_REVISION = _mod_revision.NULL_REVISION
 
1332
        # Set a cache with a size of 10 - this suffices for bzr.dev but may be
 
1333
        # too small for large or very branchy trees. However, for 55K path
 
1334
        # trees, it would be easy to use too much memory trivially. Ideally we
 
1335
        # could gauge this by looking at available real memory etc, but this is
 
1336
        # always a tricky proposition.
 
1337
        inventory_cache = lru_cache.LRUCache(10)
 
1338
        batch_size = 10 # should be ~150MB on a 55K path tree
 
1339
        batch_count = len(revision_order) / batch_size + 1
 
1340
        processed_texts = 0
 
1341
        pb.update("Calculating text parents.", processed_texts, text_count)
 
1342
        for offset in xrange(batch_count):
 
1343
            to_query = revision_order[offset * batch_size:(offset + 1) *
 
1344
                batch_size]
 
1345
            if not to_query:
 
1346
                break
 
1347
            for rev_tree in self.revision_trees(to_query):
 
1348
                revision_id = rev_tree.get_revision_id()
 
1349
                parent_ids = ancestors[revision_id]
 
1350
                for text_key in revision_keys[revision_id]:
 
1351
                    pb.update("Calculating text parents.", processed_texts)
 
1352
                    processed_texts += 1
 
1353
                    candidate_parents = []
 
1354
                    for parent_id in parent_ids:
 
1355
                        parent_text_key = (text_key[0], parent_id)
 
1356
                        try:
 
1357
                            check_parent = parent_text_key not in \
 
1358
                                revision_keys[parent_id]
 
1359
                        except KeyError:
 
1360
                            # the parent parent_id is a ghost:
 
1361
                            check_parent = False
 
1362
                            # truncate the derived graph against this ghost.
 
1363
                            parent_text_key = None
 
1364
                        if check_parent:
 
1365
                            # look at the parent commit details inventories to
 
1366
                            # determine possible candidates in the per file graph.
 
1367
                            # TODO: cache here.
 
1368
                            try:
 
1369
                                inv = inventory_cache[parent_id]
 
1370
                            except KeyError:
 
1371
                                inv = self.revision_tree(parent_id).inventory
 
1372
                                inventory_cache[parent_id] = inv
 
1373
                            parent_entry = inv._byid.get(text_key[0], None)
 
1374
                            if parent_entry is not None:
 
1375
                                parent_text_key = (
 
1376
                                    text_key[0], parent_entry.revision)
 
1377
                            else:
 
1378
                                parent_text_key = None
 
1379
                        if parent_text_key is not None:
 
1380
                            candidate_parents.append(
 
1381
                                text_key_cache[parent_text_key])
 
1382
                    parent_heads = text_graph.heads(candidate_parents)
 
1383
                    new_parents = list(parent_heads)
 
1384
                    new_parents.sort(key=lambda x:candidate_parents.index(x))
 
1385
                    if new_parents == []:
 
1386
                        new_parents = [NULL_REVISION]
 
1387
                    text_index[text_key] = new_parents
 
1388
 
 
1389
        for text_key in invalid_keys:
 
1390
            text_index[text_key] = [NULL_REVISION]
 
1391
        return text_index
 
1392
 
 
1393
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
1394
        """Get an iterable listing the keys of all the data introduced by a set
 
1395
        of revision IDs.
 
1396
 
 
1397
        The keys will be ordered so that the corresponding items can be safely
 
1398
        fetched and inserted in that order.
 
1399
 
 
1400
        :returns: An iterable producing tuples of (knit-kind, file-id,
 
1401
            versions).  knit-kind is one of 'file', 'inventory', 'signatures',
 
1402
            'revisions'.  file-id is None unless knit-kind is 'file'.
 
1403
        """
 
1404
        # XXX: it's a bit weird to control the inventory weave caching in this
 
1405
        # generator.  Ideally the caching would be done in fetch.py I think.  Or
 
1406
        # maybe this generator should explicitly have the contract that it
 
1407
        # should not be iterated until the previously yielded item has been
 
1408
        # processed?
 
1409
        self.lock_read()
 
1410
        inv_w = self.get_inventory_weave()
 
1411
        inv_w.enable_cache()
 
1412
 
 
1413
        # file ids that changed
 
1414
        file_ids = self.fileids_altered_by_revision_ids(revision_ids)
 
1415
        count = 0
 
1416
        num_file_ids = len(file_ids)
 
1417
        for file_id, altered_versions in file_ids.iteritems():
 
1418
            if _files_pb is not None:
 
1419
                _files_pb.update("fetch texts", count, num_file_ids)
 
1420
            count += 1
 
1421
            yield ("file", file_id, altered_versions)
 
1422
        # We're done with the files_pb.  Note that it finished by the caller,
 
1423
        # just as it was created by the caller.
 
1424
        del _files_pb
 
1425
 
 
1426
        # inventory
 
1427
        yield ("inventory", None, revision_ids)
 
1428
        inv_w.clear_cache()
 
1429
 
 
1430
        # signatures
 
1431
        revisions_with_signatures = set()
 
1432
        for rev_id in revision_ids:
 
1433
            try:
 
1434
                self.get_signature_text(rev_id)
 
1435
            except errors.NoSuchRevision:
 
1436
                # not signed.
 
1437
                pass
 
1438
            else:
 
1439
                revisions_with_signatures.add(rev_id)
 
1440
        self.unlock()
 
1441
        yield ("signatures", None, revisions_with_signatures)
 
1442
 
 
1443
        # revisions
 
1444
        yield ("revisions", None, revision_ids)
617
1445
 
618
1446
    @needs_read_lock
619
1447
    def get_inventory_weave(self):
623
1451
    @needs_read_lock
624
1452
    def get_inventory(self, revision_id):
625
1453
        """Get Inventory object by hash."""
626
 
        # TODO: jam 20070210 Technically we don't need to sanitize, since all
627
 
        #       called functions must sanitize.
628
 
        revision_id = osutils.safe_revision_id(revision_id)
629
1454
        return self.deserialise_inventory(
630
1455
            revision_id, self.get_inventory_xml(revision_id))
631
1456
 
635
1460
        :param revision_id: The expected revision id of the inventory.
636
1461
        :param xml: A serialised inventory.
637
1462
        """
638
 
        revision_id = osutils.safe_revision_id(revision_id)
639
 
        result = self._serializer.read_inventory_from_string(xml)
640
 
        result.root.revision = revision_id
641
 
        return result
 
1463
        return self._serializer.read_inventory_from_string(xml, revision_id)
642
1464
 
643
1465
    def serialise_inventory(self, inv):
644
1466
        return self._serializer.write_inventory_to_string(inv)
645
1467
 
 
1468
    def _serialise_inventory_to_lines(self, inv):
 
1469
        return self._serializer.write_inventory_to_lines(inv)
 
1470
 
 
1471
    def get_serializer_format(self):
 
1472
        return self._serializer.format_num
 
1473
 
646
1474
    @needs_read_lock
647
1475
    def get_inventory_xml(self, revision_id):
648
1476
        """Get inventory XML as a file object."""
649
 
        revision_id = osutils.safe_revision_id(revision_id)
650
1477
        try:
651
1478
            assert isinstance(revision_id, str), type(revision_id)
652
1479
            iw = self.get_inventory_weave()
658
1485
    def get_inventory_sha1(self, revision_id):
659
1486
        """Return the sha1 hash of the inventory entry
660
1487
        """
661
 
        # TODO: jam 20070210 Shouldn't this be deprecated / removed?
662
 
        revision_id = osutils.safe_revision_id(revision_id)
663
1488
        return self.get_revision(revision_id).inventory_sha1
664
1489
 
665
1490
    @needs_read_lock
666
1491
    def get_revision_graph(self, revision_id=None):
667
1492
        """Return a dictionary containing the revision graph.
668
 
        
 
1493
 
 
1494
        NB: This method should not be used as it accesses the entire graph all
 
1495
        at once, which is much more data than most operations should require.
 
1496
 
669
1497
        :param revision_id: The revision_id to get a graph from. If None, then
670
1498
        the entire revision graph is returned. This is a deprecated mode of
671
1499
        operation and will be removed in the future.
672
1500
        :return: a dictionary of revision_id->revision_parents_list.
673
1501
        """
674
 
        # special case NULL_REVISION
675
 
        if revision_id == _mod_revision.NULL_REVISION:
676
 
            return {}
677
 
        revision_id = osutils.safe_revision_id(revision_id)
678
 
        a_weave = self.get_inventory_weave()
679
 
        all_revisions = self._eliminate_revisions_not_present(
680
 
                                a_weave.versions())
681
 
        entire_graph = dict([(node, a_weave.get_parents(node)) for 
682
 
                             node in all_revisions])
683
 
        if revision_id is None:
684
 
            return entire_graph
685
 
        elif revision_id not in entire_graph:
686
 
            raise errors.NoSuchRevision(self, revision_id)
687
 
        else:
688
 
            # add what can be reached from revision_id
689
 
            result = {}
690
 
            pending = set([revision_id])
691
 
            while len(pending) > 0:
692
 
                node = pending.pop()
693
 
                result[node] = entire_graph[node]
694
 
                for revision_id in result[node]:
695
 
                    if revision_id not in result:
696
 
                        pending.add(revision_id)
697
 
            return result
 
1502
        raise NotImplementedError(self.get_revision_graph)
698
1503
 
699
1504
    @needs_read_lock
700
1505
    def get_revision_graph_with_ghosts(self, revision_ids=None):
703
1508
        :param revision_ids: an iterable of revisions to graph or None for all.
704
1509
        :return: a Graph object with the graph reachable from revision_ids.
705
1510
        """
706
 
        result = graph.Graph()
 
1511
        if 'evil' in debug.debug_flags:
 
1512
            mutter_callsite(3,
 
1513
                "get_revision_graph_with_ghosts scales with size of history.")
 
1514
        result = deprecated_graph.Graph()
707
1515
        if not revision_ids:
708
1516
            pending = set(self.all_revision_ids())
709
1517
            required = set([])
710
1518
        else:
711
 
            pending = set(osutils.safe_revision_id(r) for r in revision_ids)
 
1519
            pending = set(revision_ids)
712
1520
            # special case NULL_REVISION
713
1521
            if _mod_revision.NULL_REVISION in pending:
714
1522
                pending.remove(_mod_revision.NULL_REVISION)
747
1555
        :param revision_id: The revision id to start with.  All its lefthand
748
1556
            ancestors will be traversed.
749
1557
        """
750
 
        revision_id = osutils.safe_revision_id(revision_id)
751
1558
        if revision_id in (None, _mod_revision.NULL_REVISION):
752
1559
            return
753
1560
        next_id = revision_id
789
1596
        reconciler.reconcile()
790
1597
        return reconciler
791
1598
 
 
1599
    def _refresh_data(self):
 
1600
        """Helper called from lock_* to ensure coherency with disk.
 
1601
 
 
1602
        The default implementation does nothing; it is however possible
 
1603
        for repositories to maintain loaded indices across multiple locks
 
1604
        by checking inside their implementation of this method to see
 
1605
        whether their indices are still valid. This depends of course on
 
1606
        the disk format being validatable in this manner.
 
1607
        """
 
1608
 
792
1609
    @needs_read_lock
793
1610
    def revision_tree(self, revision_id):
794
1611
        """Return Tree for a revision on this branch.
801
1618
            return RevisionTree(self, Inventory(root_id=None), 
802
1619
                                _mod_revision.NULL_REVISION)
803
1620
        else:
804
 
            revision_id = osutils.safe_revision_id(revision_id)
805
1621
            inv = self.get_revision_inventory(revision_id)
806
1622
            return RevisionTree(self, inv, revision_id)
807
1623
 
818
1634
            yield RevisionTree(self, inv, revision_id)
819
1635
 
820
1636
    @needs_read_lock
821
 
    def get_ancestry(self, revision_id):
 
1637
    def get_ancestry(self, revision_id, topo_sorted=True):
822
1638
        """Return a list of revision-ids integrated by a revision.
823
1639
 
824
1640
        The first element of the list is always None, indicating the origin 
827
1643
        
828
1644
        This is topologically sorted.
829
1645
        """
830
 
        if revision_id is None:
 
1646
        if _mod_revision.is_null(revision_id):
831
1647
            return [None]
832
 
        revision_id = osutils.safe_revision_id(revision_id)
833
1648
        if not self.has_revision(revision_id):
834
1649
            raise errors.NoSuchRevision(self, revision_id)
835
1650
        w = self.get_inventory_weave()
836
 
        candidates = w.get_ancestry(revision_id)
 
1651
        candidates = w.get_ancestry(revision_id, topo_sorted)
837
1652
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
838
1653
 
 
1654
    def pack(self):
 
1655
        """Compress the data within the repository.
 
1656
 
 
1657
        This operation only makes sense for some repository types. For other
 
1658
        types it should be a no-op that just returns.
 
1659
 
 
1660
        This stub method does not require a lock, but subclasses should use
 
1661
        @needs_write_lock as this is a long running call its reasonable to 
 
1662
        implicitly lock for the user.
 
1663
        """
 
1664
 
839
1665
    @needs_read_lock
840
1666
    def print_file(self, file, revision_id):
841
1667
        """Print `file` to stdout.
844
1670
        - it writes to stdout, it assumes that that is valid etc. Fix
845
1671
        by creating a new more flexible convenience function.
846
1672
        """
847
 
        revision_id = osutils.safe_revision_id(revision_id)
848
1673
        tree = self.revision_tree(revision_id)
849
1674
        # use inventory as it was in that revision
850
1675
        file_id = tree.inventory.path2id(file)
859
1684
        return self.control_files.get_transaction()
860
1685
 
861
1686
    def revision_parents(self, revision_id):
862
 
        revision_id = osutils.safe_revision_id(revision_id)
863
1687
        return self.get_inventory_weave().parent_names(revision_id)
864
1688
 
 
1689
    @deprecated_method(symbol_versioning.one_one)
 
1690
    def get_parents(self, revision_ids):
 
1691
        """See StackedParentsProvider.get_parents"""
 
1692
        parent_map = self.get_parent_map(revision_ids)
 
1693
        return [parent_map.get(r, None) for r in revision_ids]
 
1694
 
 
1695
    def get_parent_map(self, keys):
 
1696
        """See graph._StackedParentsProvider.get_parent_map"""
 
1697
        parent_map = {}
 
1698
        for revision_id in keys:
 
1699
            if revision_id == _mod_revision.NULL_REVISION:
 
1700
                parent_map[revision_id] = ()
 
1701
            else:
 
1702
                try:
 
1703
                    parent_id_list = self.get_revision(revision_id).parent_ids
 
1704
                except errors.NoSuchRevision:
 
1705
                    pass
 
1706
                else:
 
1707
                    if len(parent_id_list) == 0:
 
1708
                        parent_ids = (_mod_revision.NULL_REVISION,)
 
1709
                    else:
 
1710
                        parent_ids = tuple(parent_id_list)
 
1711
                    parent_map[revision_id] = parent_ids
 
1712
        return parent_map
 
1713
 
 
1714
    def _make_parents_provider(self):
 
1715
        return self
 
1716
 
 
1717
    def get_graph(self, other_repository=None):
 
1718
        """Return the graph walker for this repository format"""
 
1719
        parents_provider = self._make_parents_provider()
 
1720
        if (other_repository is not None and
 
1721
            other_repository.bzrdir.transport.base !=
 
1722
            self.bzrdir.transport.base):
 
1723
            parents_provider = graph._StackedParentsProvider(
 
1724
                [parents_provider, other_repository._make_parents_provider()])
 
1725
        return graph.Graph(parents_provider)
 
1726
 
 
1727
    def _get_versioned_file_checker(self):
 
1728
        """Return an object suitable for checking versioned files."""
 
1729
        return _VersionedFileChecker(self)
 
1730
 
865
1731
    @needs_write_lock
866
1732
    def set_make_working_trees(self, new_value):
867
1733
        """Set the policy flag for making working trees when creating branches.
880
1746
 
881
1747
    @needs_write_lock
882
1748
    def sign_revision(self, revision_id, gpg_strategy):
883
 
        revision_id = osutils.safe_revision_id(revision_id)
884
1749
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
885
1750
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
886
1751
 
887
1752
    @needs_read_lock
888
1753
    def has_signature_for_revision_id(self, revision_id):
889
1754
        """Query for a revision signature for revision_id in the repository."""
890
 
        revision_id = osutils.safe_revision_id(revision_id)
891
1755
        return self._revision_store.has_signature(revision_id,
892
1756
                                                  self.get_transaction())
893
1757
 
894
1758
    @needs_read_lock
895
1759
    def get_signature_text(self, revision_id):
896
1760
        """Return the text for a signature."""
897
 
        revision_id = osutils.safe_revision_id(revision_id)
898
1761
        return self._revision_store.get_signature_text(revision_id,
899
1762
                                                       self.get_transaction())
900
1763
 
901
1764
    @needs_read_lock
902
 
    def check(self, revision_ids):
 
1765
    def check(self, revision_ids=None):
903
1766
        """Check consistency of all history of given revision_ids.
904
1767
 
905
1768
        Different repository implementations should override _check().
907
1770
        :param revision_ids: A non-empty list of revision_ids whose ancestry
908
1771
             will be checked.  Typically the last revision_id of a branch.
909
1772
        """
910
 
        if not revision_ids:
911
 
            raise ValueError("revision_ids must be non-empty in %s.check" 
912
 
                    % (self,))
913
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
914
1773
        return self._check(revision_ids)
915
1774
 
916
1775
    def _check(self, revision_ids):
944
1803
                    revision_id.decode('ascii')
945
1804
                except UnicodeDecodeError:
946
1805
                    raise errors.NonAsciiRevisionId(method, self)
947
 
 
948
 
 
949
 
 
 
1806
    
 
1807
    def revision_graph_can_have_wrong_parents(self):
 
1808
        """Is it possible for this repository to have a revision graph with
 
1809
        incorrect parents?
 
1810
 
 
1811
        If True, then this repository must also implement
 
1812
        _find_inconsistent_revision_parents so that check and reconcile can
 
1813
        check for inconsistencies before proceeding with other checks that may
 
1814
        depend on the revision index being consistent.
 
1815
        """
 
1816
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
 
1817
        
950
1818
# remove these delegates a while after bzr 0.15
951
1819
def __make_delegated(name, from_module):
952
1820
    def _deprecated_repository_forwarder():
983
1851
 
984
1852
def install_revision(repository, rev, revision_tree):
985
1853
    """Install all revision data into a repository."""
 
1854
    install_revisions(repository, [(rev, revision_tree, None)])
 
1855
 
 
1856
 
 
1857
def install_revisions(repository, iterable):
 
1858
    """Install all revision data into a repository.
 
1859
 
 
1860
    Accepts an iterable of revision, tree, signature tuples.  The signature
 
1861
    may be None.
 
1862
    """
 
1863
    repository.start_write_group()
 
1864
    try:
 
1865
        for revision, revision_tree, signature in iterable:
 
1866
            _install_revision(repository, revision, revision_tree, signature)
 
1867
    except:
 
1868
        repository.abort_write_group()
 
1869
        raise
 
1870
    else:
 
1871
        repository.commit_write_group()
 
1872
 
 
1873
 
 
1874
def _install_revision(repository, rev, revision_tree, signature):
 
1875
    """Install all revision data into a repository."""
986
1876
    present_parents = []
987
1877
    parent_trees = {}
988
1878
    for p_id in rev.parent_ids:
994
1884
 
995
1885
    inv = revision_tree.inventory
996
1886
    entries = inv.iter_entries()
997
 
    # backwards compatability hack: skip the root id.
 
1887
    # backwards compatibility hack: skip the root id.
998
1888
    if not repository.supports_rich_root():
999
1889
        path, root = entries.next()
1000
1890
        if root.revision != rev.revision_id:
1026
1916
        repository.add_inventory(rev.revision_id, inv, present_parents)
1027
1917
    except errors.RevisionAlreadyPresent:
1028
1918
        pass
 
1919
    if signature is not None:
 
1920
        repository.add_signature_text(rev.revision_id, signature)
1029
1921
    repository.add_revision(rev.revision_id, rev, inv)
1030
1922
 
1031
1923
 
1071
1963
 
1072
1964
 
1073
1965
class RepositoryFormatRegistry(registry.Registry):
1074
 
    """Registry of RepositoryFormats.
1075
 
    """
 
1966
    """Registry of RepositoryFormats."""
1076
1967
 
1077
1968
    def get(self, format_string):
1078
1969
        r = registry.Registry.get(self, format_string)
1101
1992
       children.
1102
1993
     * an open routine which returns a Repository instance.
1103
1994
 
 
1995
    There is one and only one Format subclass for each on-disk format. But
 
1996
    there can be one Repository subclass that is used for several different
 
1997
    formats. The _format attribute on a Repository instance can be used to
 
1998
    determine the disk format.
 
1999
 
1104
2000
    Formats are placed in an dict by their format string for reference 
1105
2001
    during opening. These should be subclasses of RepositoryFormat
1106
2002
    for consistency.
1113
2009
    _matchingbzrdir - the bzrdir format that the repository format was
1114
2010
    originally written to work with. This can be used if manually
1115
2011
    constructing a bzrdir and repository, or more commonly for test suite
1116
 
    parameterisation.
 
2012
    parameterization.
1117
2013
    """
1118
2014
 
 
2015
    # Set to True or False in derived classes. True indicates that the format
 
2016
    # supports ghosts gracefully.
 
2017
    supports_ghosts = None
 
2018
 
1119
2019
    def __str__(self):
1120
2020
        return "<%s>" % self.__class__.__name__
1121
2021
 
1302
2202
    'bzrlib.repofmt.weaverepo',
1303
2203
    'RepositoryFormat7'
1304
2204
    )
1305
 
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1306
 
# default control directory format
1307
2205
 
1308
2206
format_registry.register_lazy(
1309
2207
    'Bazaar-NG Knit Repository Format 1',
1310
2208
    'bzrlib.repofmt.knitrepo',
1311
2209
    'RepositoryFormatKnit1',
1312
2210
    )
1313
 
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
1314
2211
 
1315
2212
format_registry.register_lazy(
1316
2213
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1318
2215
    'RepositoryFormatKnit3',
1319
2216
    )
1320
2217
 
 
2218
format_registry.register_lazy(
 
2219
    'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
 
2220
    'bzrlib.repofmt.knitrepo',
 
2221
    'RepositoryFormatKnit4',
 
2222
    )
 
2223
 
 
2224
# Pack-based formats. There is one format for pre-subtrees, and one for
 
2225
# post-subtrees to allow ease of testing.
 
2226
# NOTE: These are experimental in 0.92.
 
2227
format_registry.register_lazy(
 
2228
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
 
2229
    'bzrlib.repofmt.pack_repo',
 
2230
    'RepositoryFormatKnitPack1',
 
2231
    )
 
2232
format_registry.register_lazy(
 
2233
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
 
2234
    'bzrlib.repofmt.pack_repo',
 
2235
    'RepositoryFormatKnitPack3',
 
2236
    )
 
2237
format_registry.register_lazy(
 
2238
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
 
2239
    'bzrlib.repofmt.pack_repo',
 
2240
    'RepositoryFormatKnitPack4',
 
2241
    )
 
2242
 
1321
2243
 
1322
2244
class InterRepository(InterObject):
1323
2245
    """This class represents operations taking place between two repositories.
1337
2259
    def copy_content(self, revision_id=None):
1338
2260
        raise NotImplementedError(self.copy_content)
1339
2261
 
1340
 
    def fetch(self, revision_id=None, pb=None):
 
2262
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
1341
2263
        """Fetch the content required to construct revision_id.
1342
2264
 
1343
2265
        The content is copied from self.source to self.target.
1353
2275
        raise NotImplementedError(self.fetch)
1354
2276
   
1355
2277
    @needs_read_lock
1356
 
    def missing_revision_ids(self, revision_id=None):
 
2278
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
1357
2279
        """Return the revision ids that source has that target does not.
1358
2280
        
1359
2281
        These are returned in topological order.
1364
2286
        # generic, possibly worst case, slow code path.
1365
2287
        target_ids = set(self.target.all_revision_ids())
1366
2288
        if revision_id is not None:
1367
 
            # TODO: jam 20070210 InterRepository is internal enough that it
1368
 
            #       should assume revision_ids are already utf-8
1369
 
            revision_id = osutils.safe_revision_id(revision_id)
1370
2289
            source_ids = self.source.get_ancestry(revision_id)
1371
2290
            assert source_ids[0] is None
1372
2291
            source_ids.pop(0)
1378
2297
        # that we've decided we need.
1379
2298
        return [rev_id for rev_id in source_ids if rev_id in result_set]
1380
2299
 
 
2300
    @staticmethod
 
2301
    def _same_model(source, target):
 
2302
        """True if source and target have the same data representation."""
 
2303
        if source.supports_rich_root() != target.supports_rich_root():
 
2304
            return False
 
2305
        if source._serializer != target._serializer:
 
2306
            return False
 
2307
        return True
 
2308
 
1381
2309
 
1382
2310
class InterSameDataRepository(InterRepository):
1383
2311
    """Code for converting between repositories that represent the same data.
1387
2315
 
1388
2316
    @classmethod
1389
2317
    def _get_repo_format_to_test(self):
1390
 
        """Repository format for testing with."""
1391
 
        return RepositoryFormat.get_default_format()
 
2318
        """Repository format for testing with.
 
2319
        
 
2320
        InterSameData can pull from subtree to subtree and from non-subtree to
 
2321
        non-subtree, so we test this with the richest repository format.
 
2322
        """
 
2323
        from bzrlib.repofmt import knitrepo
 
2324
        return knitrepo.RepositoryFormatKnit3()
1392
2325
 
1393
2326
    @staticmethod
1394
2327
    def is_compatible(source, target):
1395
 
        if source.supports_rich_root() != target.supports_rich_root():
1396
 
            return False
1397
 
        if source._serializer != target._serializer:
1398
 
            return False
1399
 
        return True
 
2328
        return InterRepository._same_model(source, target)
1400
2329
 
1401
2330
    @needs_write_lock
1402
2331
    def copy_content(self, revision_id=None):
1415
2344
            self.target.set_make_working_trees(self.source.make_working_trees())
1416
2345
        except NotImplementedError:
1417
2346
            pass
1418
 
        # TODO: jam 20070210 This is fairly internal, so we should probably
1419
 
        #       just assert that revision_id is not unicode.
1420
 
        revision_id = osutils.safe_revision_id(revision_id)
1421
2347
        # but don't bother fetching if we have the needed data now.
1422
2348
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
1423
2349
            self.target.has_revision(revision_id)):
1425
2351
        self.target.fetch(self.source, revision_id=revision_id)
1426
2352
 
1427
2353
    @needs_write_lock
1428
 
    def fetch(self, revision_id=None, pb=None):
 
2354
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
1429
2355
        """See InterRepository.fetch()."""
1430
2356
        from bzrlib.fetch import GenericRepoFetcher
1431
2357
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1432
 
               self.source, self.source._format, self.target, 
 
2358
               self.source, self.source._format, self.target,
1433
2359
               self.target._format)
1434
 
        # TODO: jam 20070210 This should be an assert, not a translate
1435
 
        revision_id = osutils.safe_revision_id(revision_id)
1436
2360
        f = GenericRepoFetcher(to_repository=self.target,
1437
2361
                               from_repository=self.source,
1438
2362
                               last_revision=revision_id,
1441
2365
 
1442
2366
 
1443
2367
class InterWeaveRepo(InterSameDataRepository):
1444
 
    """Optimised code paths between Weave based repositories."""
 
2368
    """Optimised code paths between Weave based repositories.
 
2369
    
 
2370
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
 
2371
    implemented lazy inter-object optimisation.
 
2372
    """
1445
2373
 
1446
2374
    @classmethod
1447
2375
    def _get_repo_format_to_test(self):
1475
2403
    def copy_content(self, revision_id=None):
1476
2404
        """See InterRepository.copy_content()."""
1477
2405
        # weave specific optimised path:
1478
 
        # TODO: jam 20070210 Internal, should be an assert, not translate
1479
 
        revision_id = osutils.safe_revision_id(revision_id)
1480
2406
        try:
1481
2407
            self.target.set_make_working_trees(self.source.make_working_trees())
1482
2408
        except NotImplementedError:
1504
2430
            self.target.fetch(self.source, revision_id=revision_id)
1505
2431
 
1506
2432
    @needs_write_lock
1507
 
    def fetch(self, revision_id=None, pb=None):
 
2433
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
1508
2434
        """See InterRepository.fetch()."""
1509
2435
        from bzrlib.fetch import GenericRepoFetcher
1510
2436
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1511
2437
               self.source, self.source._format, self.target, self.target._format)
1512
 
        # TODO: jam 20070210 This should be an assert, not a translate
1513
 
        revision_id = osutils.safe_revision_id(revision_id)
1514
2438
        f = GenericRepoFetcher(to_repository=self.target,
1515
2439
                               from_repository=self.source,
1516
2440
                               last_revision=revision_id,
1518
2442
        return f.count_copied, f.failed_revisions
1519
2443
 
1520
2444
    @needs_read_lock
1521
 
    def missing_revision_ids(self, revision_id=None):
 
2445
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
1522
2446
        """See InterRepository.missing_revision_ids()."""
1523
2447
        # we want all revisions to satisfy revision_id in source.
1524
2448
        # but we don't want to stat every file here and there.
1575
2499
        could lead to confusing results, and there is no need to be 
1576
2500
        overly general.
1577
2501
        """
1578
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
 
2502
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
1579
2503
        try:
1580
 
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1581
 
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
2504
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
2505
                isinstance(target._format, RepositoryFormatKnit))
1582
2506
        except AttributeError:
1583
2507
            return False
 
2508
        return are_knits and InterRepository._same_model(source, target)
1584
2509
 
1585
2510
    @needs_write_lock
1586
 
    def fetch(self, revision_id=None, pb=None):
 
2511
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
1587
2512
        """See InterRepository.fetch()."""
1588
2513
        from bzrlib.fetch import KnitRepoFetcher
1589
2514
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1590
2515
               self.source, self.source._format, self.target, self.target._format)
1591
 
        # TODO: jam 20070210 This should be an assert, not a translate
1592
 
        revision_id = osutils.safe_revision_id(revision_id)
1593
2516
        f = KnitRepoFetcher(to_repository=self.target,
1594
2517
                            from_repository=self.source,
1595
2518
                            last_revision=revision_id,
1597
2520
        return f.count_copied, f.failed_revisions
1598
2521
 
1599
2522
    @needs_read_lock
1600
 
    def missing_revision_ids(self, revision_id=None):
 
2523
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
1601
2524
        """See InterRepository.missing_revision_ids()."""
1602
2525
        if revision_id is not None:
1603
2526
            source_ids = self.source.get_ancestry(revision_id)
1604
2527
            assert source_ids[0] is None
1605
2528
            source_ids.pop(0)
1606
2529
        else:
1607
 
            source_ids = self.source._all_possible_ids()
 
2530
            source_ids = self.source.all_revision_ids()
1608
2531
        source_ids_set = set(source_ids)
1609
2532
        # source_ids is the worst possible case we may need to pull.
1610
2533
        # now we want to filter source_ids against what we actually
1611
2534
        # have in target, but don't try to check for existence where we know
1612
2535
        # we do not have a revision as that would be pointless.
1613
 
        target_ids = set(self.target._all_possible_ids())
 
2536
        target_ids = set(self.target.all_revision_ids())
1614
2537
        possibly_present_revisions = target_ids.intersection(source_ids_set)
1615
2538
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
1616
2539
        required_revisions = source_ids_set.difference(actually_present_revisions)
1627
2550
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
1628
2551
 
1629
2552
 
 
2553
class InterPackRepo(InterSameDataRepository):
 
2554
    """Optimised code paths between Pack based repositories."""
 
2555
 
 
2556
    @classmethod
 
2557
    def _get_repo_format_to_test(self):
 
2558
        from bzrlib.repofmt import pack_repo
 
2559
        return pack_repo.RepositoryFormatKnitPack1()
 
2560
 
 
2561
    @staticmethod
 
2562
    def is_compatible(source, target):
 
2563
        """Be compatible with known Pack formats.
 
2564
        
 
2565
        We don't test for the stores being of specific types because that
 
2566
        could lead to confusing results, and there is no need to be 
 
2567
        overly general.
 
2568
        """
 
2569
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
 
2570
        try:
 
2571
            are_packs = (isinstance(source._format, RepositoryFormatPack) and
 
2572
                isinstance(target._format, RepositoryFormatPack))
 
2573
        except AttributeError:
 
2574
            return False
 
2575
        return are_packs and InterRepository._same_model(source, target)
 
2576
 
 
2577
    @needs_write_lock
 
2578
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2579
        """See InterRepository.fetch()."""
 
2580
        from bzrlib.repofmt.pack_repo import Packer
 
2581
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2582
               self.source, self.source._format, self.target, self.target._format)
 
2583
        self.count_copied = 0
 
2584
        if revision_id is None:
 
2585
            # TODO:
 
2586
            # everything to do - use pack logic
 
2587
            # to fetch from all packs to one without
 
2588
            # inventory parsing etc, IFF nothing to be copied is in the target.
 
2589
            # till then:
 
2590
            revision_ids = self.source.all_revision_ids()
 
2591
            # implementing the TODO will involve:
 
2592
            # - detecting when all of a pack is selected
 
2593
            # - avoiding as much as possible pre-selection, so the
 
2594
            # more-core routines such as create_pack_from_packs can filter in
 
2595
            # a just-in-time fashion. (though having a HEADS list on a
 
2596
            # repository might make this a lot easier, because we could
 
2597
            # sensibly detect 'new revisions' without doing a full index scan.
 
2598
        elif _mod_revision.is_null(revision_id):
 
2599
            # nothing to do:
 
2600
            return (0, [])
 
2601
        else:
 
2602
            try:
 
2603
                revision_ids = self.missing_revision_ids(revision_id,
 
2604
                    find_ghosts=find_ghosts)
 
2605
            except errors.NoSuchRevision:
 
2606
                raise errors.InstallFailed([revision_id])
 
2607
        packs = self.source._pack_collection.all_packs()
 
2608
        pack = Packer(self.target._pack_collection, packs, '.fetch',
 
2609
            revision_ids).pack()
 
2610
        if pack is not None:
 
2611
            self.target._pack_collection._save_pack_names()
 
2612
            # Trigger an autopack. This may duplicate effort as we've just done
 
2613
            # a pack creation, but for now it is simpler to think about as
 
2614
            # 'upload data, then repack if needed'.
 
2615
            self.target._pack_collection.autopack()
 
2616
            return (pack.get_revision_count(), [])
 
2617
        else:
 
2618
            return (0, [])
 
2619
 
 
2620
    @needs_read_lock
 
2621
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
2622
        """See InterRepository.missing_revision_ids().
 
2623
        
 
2624
        :param find_ghosts: Find ghosts throughough the ancestry of
 
2625
            revision_id.
 
2626
        """
 
2627
        if not find_ghosts and revision_id is not None:
 
2628
            graph = self.source.get_graph()
 
2629
            missing_revs = set()
 
2630
            searcher = graph._make_breadth_first_searcher([revision_id])
 
2631
            target_index = \
 
2632
                self.target._pack_collection.revision_index.combined_index
 
2633
            null_set = frozenset([_mod_revision.NULL_REVISION])
 
2634
            while True:
 
2635
                try:
 
2636
                    next_revs = set(searcher.next())
 
2637
                except StopIteration:
 
2638
                    break
 
2639
                next_revs.difference_update(null_set)
 
2640
                target_keys = [(key,) for key in next_revs]
 
2641
                have_revs = frozenset(node[1][0] for node in
 
2642
                    target_index.iter_entries(target_keys))
 
2643
                missing_revs.update(next_revs - have_revs)
 
2644
                searcher.stop_searching_any(have_revs)
 
2645
            if next_revs - have_revs == set([revision_id]):
 
2646
                # we saw the start rev itself, but no parents from it (or
 
2647
                # next_revs would have been updated to e.g. set(). We remove
 
2648
                # have_revs because if we found revision_id locally we
 
2649
                # stop_searching at the first time around.
 
2650
                raise errors.NoSuchRevision(self.source, revision_id)
 
2651
            return missing_revs
 
2652
        elif revision_id is not None:
 
2653
            source_ids = self.source.get_ancestry(revision_id)
 
2654
            assert source_ids[0] is None
 
2655
            source_ids.pop(0)
 
2656
        else:
 
2657
            source_ids = self.source.all_revision_ids()
 
2658
        # source_ids is the worst possible case we may need to pull.
 
2659
        # now we want to filter source_ids against what we actually
 
2660
        # have in target, but don't try to check for existence where we know
 
2661
        # we do not have a revision as that would be pointless.
 
2662
        target_ids = set(self.target.all_revision_ids())
 
2663
        return [r for r in source_ids if (r not in target_ids)]
 
2664
 
 
2665
 
1630
2666
class InterModel1and2(InterRepository):
1631
2667
 
1632
2668
    @classmethod
1641
2677
            return False
1642
2678
 
1643
2679
    @needs_write_lock
1644
 
    def fetch(self, revision_id=None, pb=None):
 
2680
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
1645
2681
        """See InterRepository.fetch()."""
1646
2682
        from bzrlib.fetch import Model1toKnit2Fetcher
1647
 
        # TODO: jam 20070210 This should be an assert, not a translate
1648
 
        revision_id = osutils.safe_revision_id(revision_id)
1649
2683
        f = Model1toKnit2Fetcher(to_repository=self.target,
1650
2684
                                 from_repository=self.source,
1651
2685
                                 last_revision=revision_id,
1666
2700
            self.target.set_make_working_trees(self.source.make_working_trees())
1667
2701
        except NotImplementedError:
1668
2702
            pass
1669
 
        # TODO: jam 20070210 Internal, assert, don't translate
1670
 
        revision_id = osutils.safe_revision_id(revision_id)
1671
2703
        # but don't bother fetching if we have the needed data now.
1672
2704
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
1673
2705
            self.target.has_revision(revision_id)):
1686
2718
        """Be compatible with Knit1 source and Knit3 target"""
1687
2719
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
1688
2720
        try:
1689
 
            from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1690
 
                    RepositoryFormatKnit3
1691
 
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1692
 
                    isinstance(target._format, (RepositoryFormatKnit3)))
 
2721
            from bzrlib.repofmt.knitrepo import (RepositoryFormatKnit1,
 
2722
                RepositoryFormatKnit3)
 
2723
            from bzrlib.repofmt.pack_repo import (RepositoryFormatKnitPack1,
 
2724
                RepositoryFormatKnitPack3)
 
2725
            return (isinstance(source._format,
 
2726
                    (RepositoryFormatKnit1, RepositoryFormatKnitPack1)) and
 
2727
                isinstance(target._format,
 
2728
                    (RepositoryFormatKnit3, RepositoryFormatKnitPack3))
 
2729
                )
1693
2730
        except AttributeError:
1694
2731
            return False
1695
2732
 
1696
2733
    @needs_write_lock
1697
 
    def fetch(self, revision_id=None, pb=None):
 
2734
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
1698
2735
        """See InterRepository.fetch()."""
1699
2736
        from bzrlib.fetch import Knit1to2Fetcher
1700
2737
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1701
2738
               self.source, self.source._format, self.target, 
1702
2739
               self.target._format)
1703
 
        # TODO: jam 20070210 This should be an assert, not a translate
1704
 
        revision_id = osutils.safe_revision_id(revision_id)
1705
2740
        f = Knit1to2Fetcher(to_repository=self.target,
1706
2741
                            from_repository=self.source,
1707
2742
                            last_revision=revision_id,
1709
2744
        return f.count_copied, f.failed_revisions
1710
2745
 
1711
2746
 
1712
 
class InterRemoteRepository(InterRepository):
1713
 
    """Code for converting between RemoteRepository objects.
1714
 
 
1715
 
    This just gets an non-remote repository from the RemoteRepository, and calls
1716
 
    InterRepository.get again.
1717
 
    """
1718
 
 
1719
 
    def __init__(self, source, target):
1720
 
        if isinstance(source, remote.RemoteRepository):
1721
 
            source._ensure_real()
1722
 
            real_source = source._real_repository
1723
 
        else:
1724
 
            real_source = source
1725
 
        if isinstance(target, remote.RemoteRepository):
1726
 
            target._ensure_real()
1727
 
            real_target = target._real_repository
1728
 
        else:
1729
 
            real_target = target
1730
 
        self.real_inter = InterRepository.get(real_source, real_target)
1731
 
 
1732
 
    @staticmethod
1733
 
    def is_compatible(source, target):
1734
 
        if isinstance(source, remote.RemoteRepository):
1735
 
            return True
 
2747
class InterDifferingSerializer(InterKnitRepo):
 
2748
 
 
2749
    @classmethod
 
2750
    def _get_repo_format_to_test(self):
 
2751
        return None
 
2752
 
 
2753
    @staticmethod
 
2754
    def is_compatible(source, target):
 
2755
        """Be compatible with Knit2 source and Knit3 target"""
 
2756
        if source.supports_rich_root() != target.supports_rich_root():
 
2757
            return False
 
2758
        # Ideally, we'd support fetching if the source had no tree references
 
2759
        # even if it supported them...
 
2760
        if (getattr(source, '_format.supports_tree_reference', False) and
 
2761
            not getattr(target, '_format.supports_tree_reference', False)):
 
2762
            return False
 
2763
        return True
 
2764
 
 
2765
    @needs_write_lock
 
2766
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2767
        """See InterRepository.fetch()."""
 
2768
        revision_ids = self.target.missing_revision_ids(self.source,
 
2769
                                                        revision_id)
 
2770
        def revisions_iterator():
 
2771
            for current_revision_id in revision_ids:
 
2772
                revision = self.source.get_revision(current_revision_id)
 
2773
                tree = self.source.revision_tree(current_revision_id)
 
2774
                try:
 
2775
                    signature = self.source.get_signature_text(
 
2776
                        current_revision_id)
 
2777
                except errors.NoSuchRevision:
 
2778
                    signature = None
 
2779
                yield revision, tree, signature
 
2780
        install_revisions(self.target, revisions_iterator())
 
2781
        return len(revision_ids), 0
 
2782
 
 
2783
 
 
2784
class InterRemoteToOther(InterRepository):
 
2785
 
 
2786
    def __init__(self, source, target):
 
2787
        InterRepository.__init__(self, source, target)
 
2788
        self._real_inter = None
 
2789
 
 
2790
    @staticmethod
 
2791
    def is_compatible(source, target):
 
2792
        if not isinstance(source, remote.RemoteRepository):
 
2793
            return False
 
2794
        source._ensure_real()
 
2795
        real_source = source._real_repository
 
2796
        # Is source's model compatible with target's model, and are they the
 
2797
        # same format?  Currently we can only optimise fetching from an
 
2798
        # identical model & format repo.
 
2799
        assert not isinstance(real_source, remote.RemoteRepository), (
 
2800
            "We don't support remote repos backed by remote repos yet.")
 
2801
        return real_source._format == target._format
 
2802
 
 
2803
    @needs_write_lock
 
2804
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2805
        """See InterRepository.fetch()."""
 
2806
        from bzrlib.fetch import RemoteToOtherFetcher
 
2807
        mutter("Using fetch logic to copy between %s(remote) and %s(%s)",
 
2808
               self.source, self.target, self.target._format)
 
2809
        # TODO: jam 20070210 This should be an assert, not a translate
 
2810
        revision_id = osutils.safe_revision_id(revision_id)
 
2811
        f = RemoteToOtherFetcher(to_repository=self.target,
 
2812
                                 from_repository=self.source,
 
2813
                                 last_revision=revision_id,
 
2814
                                 pb=pb)
 
2815
        return f.count_copied, f.failed_revisions
 
2816
 
 
2817
    @classmethod
 
2818
    def _get_repo_format_to_test(self):
 
2819
        return None
 
2820
 
 
2821
 
 
2822
class InterOtherToRemote(InterRepository):
 
2823
 
 
2824
    def __init__(self, source, target):
 
2825
        InterRepository.__init__(self, source, target)
 
2826
        self._real_inter = None
 
2827
 
 
2828
    @staticmethod
 
2829
    def is_compatible(source, target):
1736
2830
        if isinstance(target, remote.RemoteRepository):
1737
2831
            return True
1738
2832
        return False
1739
2833
 
 
2834
    def _ensure_real_inter(self):
 
2835
        if self._real_inter is None:
 
2836
            self.target._ensure_real()
 
2837
            real_target = self.target._real_repository
 
2838
            self._real_inter = InterRepository.get(self.source, real_target)
 
2839
    
1740
2840
    def copy_content(self, revision_id=None):
1741
 
        self.real_inter.copy_content(revision_id=revision_id)
 
2841
        self._ensure_real_inter()
 
2842
        self._real_inter.copy_content(revision_id=revision_id)
1742
2843
 
1743
 
    def fetch(self, revision_id=None, pb=None):
1744
 
        self.real_inter.fetch(revision_id=revision_id, pb=pb)
 
2844
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 
2845
        self._ensure_real_inter()
 
2846
        self._real_inter.fetch(revision_id=revision_id, pb=pb)
1745
2847
 
1746
2848
    @classmethod
1747
2849
    def _get_repo_format_to_test(self):
1748
2850
        return None
1749
2851
 
1750
2852
 
 
2853
InterRepository.register_optimiser(InterDifferingSerializer)
1751
2854
InterRepository.register_optimiser(InterSameDataRepository)
1752
2855
InterRepository.register_optimiser(InterWeaveRepo)
1753
2856
InterRepository.register_optimiser(InterKnitRepo)
1754
2857
InterRepository.register_optimiser(InterModel1and2)
1755
2858
InterRepository.register_optimiser(InterKnit1and2)
1756
 
InterRepository.register_optimiser(InterRemoteRepository)
1757
 
 
1758
 
 
1759
 
class RepositoryTestProviderAdapter(object):
1760
 
    """A tool to generate a suite testing multiple repository formats at once.
1761
 
 
1762
 
    This is done by copying the test once for each transport and injecting
1763
 
    the transport_server, transport_readonly_server, and bzrdir_format and
1764
 
    repository_format classes into each copy. Each copy is also given a new id()
1765
 
    to make it easy to identify.
1766
 
    """
1767
 
 
1768
 
    def __init__(self, transport_server, transport_readonly_server, formats,
1769
 
                 vfs_transport_factory=None):
1770
 
        self._transport_server = transport_server
1771
 
        self._transport_readonly_server = transport_readonly_server
1772
 
        self._vfs_transport_factory = vfs_transport_factory
1773
 
        self._formats = formats
1774
 
    
1775
 
    def adapt(self, test):
1776
 
        result = unittest.TestSuite()
1777
 
        for repository_format, bzrdir_format in self._formats:
1778
 
            from copy import deepcopy
1779
 
            new_test = deepcopy(test)
1780
 
            new_test.transport_server = self._transport_server
1781
 
            new_test.transport_readonly_server = self._transport_readonly_server
1782
 
            # Only override the test's vfs_transport_factory if one was
1783
 
            # specified, otherwise just leave the default in place.
1784
 
            if self._vfs_transport_factory:
1785
 
                new_test.vfs_transport_factory = self._vfs_transport_factory
1786
 
            new_test.bzrdir_format = bzrdir_format
1787
 
            new_test.repository_format = repository_format
1788
 
            def make_new_test_id():
1789
 
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
1790
 
                return lambda: new_id
1791
 
            new_test.id = make_new_test_id()
1792
 
            result.addTest(new_test)
1793
 
        return result
1794
 
 
1795
 
 
1796
 
class InterRepositoryTestProviderAdapter(object):
1797
 
    """A tool to generate a suite testing multiple inter repository formats.
1798
 
 
1799
 
    This is done by copying the test once for each interrepo provider and injecting
1800
 
    the transport_server, transport_readonly_server, repository_format and 
1801
 
    repository_to_format classes into each copy.
1802
 
    Each copy is also given a new id() to make it easy to identify.
1803
 
    """
1804
 
 
1805
 
    def __init__(self, transport_server, transport_readonly_server, formats):
1806
 
        self._transport_server = transport_server
1807
 
        self._transport_readonly_server = transport_readonly_server
1808
 
        self._formats = formats
1809
 
    
1810
 
    def adapt(self, test):
1811
 
        result = unittest.TestSuite()
1812
 
        for interrepo_class, repository_format, repository_format_to in self._formats:
1813
 
            from copy import deepcopy
1814
 
            new_test = deepcopy(test)
1815
 
            new_test.transport_server = self._transport_server
1816
 
            new_test.transport_readonly_server = self._transport_readonly_server
1817
 
            new_test.interrepo_class = interrepo_class
1818
 
            new_test.repository_format = repository_format
1819
 
            new_test.repository_format_to = repository_format_to
1820
 
            def make_new_test_id():
1821
 
                new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
1822
 
                return lambda: new_id
1823
 
            new_test.id = make_new_test_id()
1824
 
            result.addTest(new_test)
1825
 
        return result
1826
 
 
1827
 
    @staticmethod
1828
 
    def default_test_list():
1829
 
        """Generate the default list of interrepo permutations to test."""
1830
 
        from bzrlib.repofmt import knitrepo, weaverepo
1831
 
        result = []
1832
 
        # test the default InterRepository between format 6 and the current 
1833
 
        # default format.
1834
 
        # XXX: robertc 20060220 reinstate this when there are two supported
1835
 
        # formats which do not have an optimal code path between them.
1836
 
        #result.append((InterRepository,
1837
 
        #               RepositoryFormat6(),
1838
 
        #               RepositoryFormatKnit1()))
1839
 
        for optimiser_class in InterRepository._optimisers:
1840
 
            format_to_test = optimiser_class._get_repo_format_to_test()
1841
 
            if format_to_test is not None:
1842
 
                result.append((optimiser_class,
1843
 
                               format_to_test, format_to_test))
1844
 
        # if there are specific combinations we want to use, we can add them 
1845
 
        # here.
1846
 
        result.append((InterModel1and2,
1847
 
                       weaverepo.RepositoryFormat5(),
1848
 
                       knitrepo.RepositoryFormatKnit3()))
1849
 
        result.append((InterKnit1and2,
1850
 
                       knitrepo.RepositoryFormatKnit1(),
1851
 
                       knitrepo.RepositoryFormatKnit3()))
1852
 
        return result
 
2859
InterRepository.register_optimiser(InterPackRepo)
 
2860
InterRepository.register_optimiser(InterRemoteToOther)
 
2861
InterRepository.register_optimiser(InterOtherToRemote)
1853
2862
 
1854
2863
 
1855
2864
class CopyConverter(object):
1904
2913
        self.pb.update(message, self.count, self.total)
1905
2914
 
1906
2915
 
1907
 
class CommitBuilder(object):
1908
 
    """Provides an interface to build up a commit.
1909
 
 
1910
 
    This allows describing a tree to be committed without needing to 
1911
 
    know the internals of the format of the repository.
1912
 
    """
1913
 
    
1914
 
    record_root_entry = False
1915
 
    def __init__(self, repository, parents, config, timestamp=None, 
1916
 
                 timezone=None, committer=None, revprops=None, 
1917
 
                 revision_id=None):
1918
 
        """Initiate a CommitBuilder.
1919
 
 
1920
 
        :param repository: Repository to commit to.
1921
 
        :param parents: Revision ids of the parents of the new revision.
1922
 
        :param config: Configuration to use.
1923
 
        :param timestamp: Optional timestamp recorded for commit.
1924
 
        :param timezone: Optional timezone for timestamp.
1925
 
        :param committer: Optional committer to set for commit.
1926
 
        :param revprops: Optional dictionary of revision properties.
1927
 
        :param revision_id: Optional revision id.
1928
 
        """
1929
 
        self._config = config
1930
 
 
1931
 
        if committer is None:
1932
 
            self._committer = self._config.username()
1933
 
        else:
1934
 
            assert isinstance(committer, basestring), type(committer)
1935
 
            self._committer = committer
1936
 
 
1937
 
        self.new_inventory = Inventory(None)
1938
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
1939
 
        self.parents = parents
1940
 
        self.repository = repository
1941
 
 
1942
 
        self._revprops = {}
1943
 
        if revprops is not None:
1944
 
            self._revprops.update(revprops)
1945
 
 
1946
 
        if timestamp is None:
1947
 
            timestamp = time.time()
1948
 
        # Restrict resolution to 1ms
1949
 
        self._timestamp = round(timestamp, 3)
1950
 
 
1951
 
        if timezone is None:
1952
 
            self._timezone = osutils.local_time_offset()
1953
 
        else:
1954
 
            self._timezone = int(timezone)
1955
 
 
1956
 
        self._generate_revision_if_needed()
1957
 
 
1958
 
    def commit(self, message):
1959
 
        """Make the actual commit.
1960
 
 
1961
 
        :return: The revision id of the recorded revision.
1962
 
        """
1963
 
        rev = _mod_revision.Revision(
1964
 
                       timestamp=self._timestamp,
1965
 
                       timezone=self._timezone,
1966
 
                       committer=self._committer,
1967
 
                       message=message,
1968
 
                       inventory_sha1=self.inv_sha1,
1969
 
                       revision_id=self._new_revision_id,
1970
 
                       properties=self._revprops)
1971
 
        rev.parent_ids = self.parents
1972
 
        self.repository.add_revision(self._new_revision_id, rev, 
1973
 
            self.new_inventory, self._config)
1974
 
        return self._new_revision_id
1975
 
 
1976
 
    def revision_tree(self):
1977
 
        """Return the tree that was just committed.
1978
 
 
1979
 
        After calling commit() this can be called to get a RevisionTree
1980
 
        representing the newly committed tree. This is preferred to
1981
 
        calling Repository.revision_tree() because that may require
1982
 
        deserializing the inventory, while we already have a copy in
1983
 
        memory.
1984
 
        """
1985
 
        return RevisionTree(self.repository, self.new_inventory,
1986
 
                            self._new_revision_id)
1987
 
 
1988
 
    def finish_inventory(self):
1989
 
        """Tell the builder that the inventory is finished."""
1990
 
        if self.new_inventory.root is None:
1991
 
            symbol_versioning.warn('Root entry should be supplied to'
1992
 
                ' record_entry_contents, as of bzr 0.10.',
1993
 
                 DeprecationWarning, stacklevel=2)
1994
 
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
1995
 
        self.new_inventory.revision_id = self._new_revision_id
1996
 
        self.inv_sha1 = self.repository.add_inventory(
1997
 
            self._new_revision_id,
1998
 
            self.new_inventory,
1999
 
            self.parents
2000
 
            )
2001
 
 
2002
 
    def _gen_revision_id(self):
2003
 
        """Return new revision-id."""
2004
 
        return generate_ids.gen_revision_id(self._config.username(),
2005
 
                                            self._timestamp)
2006
 
 
2007
 
    def _generate_revision_if_needed(self):
2008
 
        """Create a revision id if None was supplied.
2009
 
        
2010
 
        If the repository can not support user-specified revision ids
2011
 
        they should override this function and raise CannotSetRevisionId
2012
 
        if _new_revision_id is not None.
2013
 
 
2014
 
        :raises: CannotSetRevisionId
2015
 
        """
2016
 
        if self._new_revision_id is None:
2017
 
            self._new_revision_id = self._gen_revision_id()
2018
 
 
2019
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
2020
 
        """Record the content of ie from tree into the commit if needed.
2021
 
 
2022
 
        Side effect: sets ie.revision when unchanged
2023
 
 
2024
 
        :param ie: An inventory entry present in the commit.
2025
 
        :param parent_invs: The inventories of the parent revisions of the
2026
 
            commit.
2027
 
        :param path: The path the entry is at in the tree.
2028
 
        :param tree: The tree which contains this entry and should be used to 
2029
 
        obtain content.
2030
 
        """
2031
 
        if self.new_inventory.root is None and ie.parent_id is not None:
2032
 
            symbol_versioning.warn('Root entry should be supplied to'
2033
 
                ' record_entry_contents, as of bzr 0.10.',
2034
 
                 DeprecationWarning, stacklevel=2)
2035
 
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
2036
 
                                       '', tree)
2037
 
        self.new_inventory.add(ie)
2038
 
 
2039
 
        # ie.revision is always None if the InventoryEntry is considered
2040
 
        # for committing. ie.snapshot will record the correct revision 
2041
 
        # which may be the sole parent if it is untouched.
2042
 
        if ie.revision is not None:
2043
 
            return
2044
 
 
2045
 
        # In this revision format, root entries have no knit or weave
2046
 
        if ie is self.new_inventory.root:
2047
 
            # When serializing out to disk and back in
2048
 
            # root.revision is always _new_revision_id
2049
 
            ie.revision = self._new_revision_id
2050
 
            return
2051
 
        previous_entries = ie.find_previous_heads(
2052
 
            parent_invs,
2053
 
            self.repository.weave_store,
2054
 
            self.repository.get_transaction())
2055
 
        # we are creating a new revision for ie in the history store
2056
 
        # and inventory.
2057
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2058
 
 
2059
 
    def modified_directory(self, file_id, file_parents):
2060
 
        """Record the presence of a symbolic link.
2061
 
 
2062
 
        :param file_id: The file_id of the link to record.
2063
 
        :param file_parents: The per-file parent revision ids.
2064
 
        """
2065
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2066
 
 
2067
 
    def modified_reference(self, file_id, file_parents):
2068
 
        """Record the modification of a reference.
2069
 
 
2070
 
        :param file_id: The file_id of the link to record.
2071
 
        :param file_parents: The per-file parent revision ids.
2072
 
        """
2073
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2074
 
    
2075
 
    def modified_file_text(self, file_id, file_parents,
2076
 
                           get_content_byte_lines, text_sha1=None,
2077
 
                           text_size=None):
2078
 
        """Record the text of file file_id
2079
 
 
2080
 
        :param file_id: The file_id of the file to record the text of.
2081
 
        :param file_parents: The per-file parent revision ids.
2082
 
        :param get_content_byte_lines: A callable which will return the byte
2083
 
            lines for the file.
2084
 
        :param text_sha1: Optional SHA1 of the file contents.
2085
 
        :param text_size: Optional size of the file contents.
2086
 
        """
2087
 
        # mutter('storing text of file {%s} in revision {%s} into %r',
2088
 
        #        file_id, self._new_revision_id, self.repository.weave_store)
2089
 
        # special case to avoid diffing on renames or 
2090
 
        # reparenting
2091
 
        if (len(file_parents) == 1
2092
 
            and text_sha1 == file_parents.values()[0].text_sha1
2093
 
            and text_size == file_parents.values()[0].text_size):
2094
 
            previous_ie = file_parents.values()[0]
2095
 
            versionedfile = self.repository.weave_store.get_weave(file_id, 
2096
 
                self.repository.get_transaction())
2097
 
            versionedfile.clone_text(self._new_revision_id, 
2098
 
                previous_ie.revision, file_parents.keys())
2099
 
            return text_sha1, text_size
2100
 
        else:
2101
 
            new_lines = get_content_byte_lines()
2102
 
            # TODO: Rather than invoking sha_strings here, _add_text_to_weave
2103
 
            # should return the SHA1 and size
2104
 
            self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2105
 
            return osutils.sha_strings(new_lines), \
2106
 
                sum(map(len, new_lines))
2107
 
 
2108
 
    def modified_link(self, file_id, file_parents, link_target):
2109
 
        """Record the presence of a symbolic link.
2110
 
 
2111
 
        :param file_id: The file_id of the link to record.
2112
 
        :param file_parents: The per-file parent revision ids.
2113
 
        :param link_target: Target location of this link.
2114
 
        """
2115
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2116
 
 
2117
 
    def _add_text_to_weave(self, file_id, new_lines, parents):
2118
 
        versionedfile = self.repository.weave_store.get_weave_or_empty(
2119
 
            file_id, self.repository.get_transaction())
2120
 
        versionedfile.add_lines(self._new_revision_id, parents, new_lines)
2121
 
        versionedfile.clear_cache()
2122
 
 
2123
 
 
2124
 
class _CommitBuilder(CommitBuilder):
2125
 
    """Temporary class so old CommitBuilders are detected properly
2126
 
    
2127
 
    Note: CommitBuilder works whether or not root entry is recorded.
2128
 
    """
2129
 
 
2130
 
    record_root_entry = True
2131
 
 
2132
 
 
2133
 
class RootCommitBuilder(CommitBuilder):
2134
 
    """This commitbuilder actually records the root id"""
2135
 
    
2136
 
    record_root_entry = True
2137
 
 
2138
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
2139
 
        """Record the content of ie from tree into the commit if needed.
2140
 
 
2141
 
        Side effect: sets ie.revision when unchanged
2142
 
 
2143
 
        :param ie: An inventory entry present in the commit.
2144
 
        :param parent_invs: The inventories of the parent revisions of the
2145
 
            commit.
2146
 
        :param path: The path the entry is at in the tree.
2147
 
        :param tree: The tree which contains this entry and should be used to 
2148
 
        obtain content.
2149
 
        """
2150
 
        assert self.new_inventory.root is not None or ie.parent_id is None
2151
 
        self.new_inventory.add(ie)
2152
 
 
2153
 
        # ie.revision is always None if the InventoryEntry is considered
2154
 
        # for committing. ie.snapshot will record the correct revision 
2155
 
        # which may be the sole parent if it is untouched.
2156
 
        if ie.revision is not None:
2157
 
            return
2158
 
 
2159
 
        previous_entries = ie.find_previous_heads(
2160
 
            parent_invs,
2161
 
            self.repository.weave_store,
2162
 
            self.repository.get_transaction())
2163
 
        # we are creating a new revision for ie in the history store
2164
 
        # and inventory.
2165
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
2166
 
 
2167
 
 
2168
2916
_unescape_map = {
2169
2917
    'apos':"'",
2170
2918
    'quot':'"',
2193
2941
    if _unescape_re is None:
2194
2942
        _unescape_re = re.compile('\&([^;]*);')
2195
2943
    return _unescape_re.sub(_unescaper, data)
 
2944
 
 
2945
 
 
2946
class _VersionedFileChecker(object):
 
2947
 
 
2948
    def __init__(self, repository):
 
2949
        self.repository = repository
 
2950
        self.text_index = self.repository._generate_text_key_index()
 
2951
    
 
2952
    def calculate_file_version_parents(self, revision_id, file_id):
 
2953
        """Calculate the correct parents for a file version according to
 
2954
        the inventories.
 
2955
        """
 
2956
        parent_keys = self.text_index[(file_id, revision_id)]
 
2957
        if parent_keys == [_mod_revision.NULL_REVISION]:
 
2958
            return ()
 
2959
        # strip the file_id, for the weave api
 
2960
        return tuple([revision_id for file_id, revision_id in parent_keys])
 
2961
 
 
2962
    def check_file_version_parents(self, weave, file_id):
 
2963
        """Check the parents stored in a versioned file are correct.
 
2964
 
 
2965
        It also detects file versions that are not referenced by their
 
2966
        corresponding revision's inventory.
 
2967
 
 
2968
        :returns: A tuple of (wrong_parents, dangling_file_versions).
 
2969
            wrong_parents is a dict mapping {revision_id: (stored_parents,
 
2970
            correct_parents)} for each revision_id where the stored parents
 
2971
            are not correct.  dangling_file_versions is a set of (file_id,
 
2972
            revision_id) tuples for versions that are present in this versioned
 
2973
            file, but not used by the corresponding inventory.
 
2974
        """
 
2975
        wrong_parents = {}
 
2976
        unused_versions = set()
 
2977
        for num, revision_id in enumerate(weave.versions()):
 
2978
            try:
 
2979
                correct_parents = self.calculate_file_version_parents(
 
2980
                    revision_id, file_id)
 
2981
            except KeyError:
 
2982
                # The version is not part of the used keys.
 
2983
                unused_versions.add(revision_id)
 
2984
            else:
 
2985
                try:
 
2986
                    knit_parents = tuple(weave.get_parents(revision_id))
 
2987
                except errors.RevisionNotPresent:
 
2988
                    knit_parents = None
 
2989
                if correct_parents != knit_parents:
 
2990
                    wrong_parents[revision_id] = (knit_parents, correct_parents)
 
2991
        return wrong_parents, unused_versions