~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Aaron Bentley
  • Date: 2007-07-04 23:38:26 UTC
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: aaron.bentley@utoronto.ca-20070704233826-jkp63376wi1n96mm
update docs

Show diffs side-by-side

added added

removed removed

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