~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2007-10-10 00:21:57 UTC
  • mfrom: (2900 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2901.
  • Revision ID: mbp@sourcefrog.net-20071010002157-utci0x44m2w47wgd
merge news

Show diffs side-by-side

added added

removed removed

Lines of Context:
96
96
            self._committer = committer
97
97
 
98
98
        self.new_inventory = Inventory(None)
99
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
 
99
        self._new_revision_id = revision_id
100
100
        self.parents = parents
101
101
        self.repository = repository
102
102
 
115
115
            self._timezone = int(timezone)
116
116
 
117
117
        self._generate_revision_if_needed()
 
118
        self._repo_graph = repository.get_graph()
118
119
 
119
120
    def commit(self, message):
120
121
        """Make the actual commit.
194
195
            commit.
195
196
        :param tree: The tree that is being committed.
196
197
        """
197
 
        if ie.parent_id is not None:
198
 
            # if ie is not root, add a root automatically.
199
 
            symbol_versioning.warn('Root entry should be supplied to'
200
 
                ' record_entry_contents, as of bzr 0.10.',
201
 
                 DeprecationWarning, stacklevel=2)
202
 
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
203
 
                                       '', tree)
 
198
        # In this revision format, root entries have no knit or weave When
 
199
        # serializing out to disk and back in root.revision is always
 
200
        # _new_revision_id
 
201
        ie.revision = self._new_revision_id
 
202
 
 
203
    def _get_delta(self, ie, basis_inv, path):
 
204
        """Get a delta against the basis inventory for ie."""
 
205
        if ie.file_id not in basis_inv:
 
206
            # add
 
207
            return (None, path, ie.file_id, ie)
 
208
        elif ie != basis_inv[ie.file_id]:
 
209
            # common but altered
 
210
            # TODO: avoid tis id2path call.
 
211
            return (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
204
212
        else:
205
 
            # In this revision format, root entries have no knit or weave When
206
 
            # serializing out to disk and back in root.revision is always
207
 
            # _new_revision_id
208
 
            ie.revision = self._new_revision_id
 
213
            # common, unaltered
 
214
            return None
209
215
 
210
 
    def record_entry_contents(self, ie, parent_invs, path, tree):
 
216
    def record_entry_contents(self, ie, parent_invs, path, tree,
 
217
        content_summary):
211
218
        """Record the content of ie from tree into the commit if needed.
212
219
 
213
220
        Side effect: sets ie.revision when unchanged
217
224
            commit.
218
225
        :param path: The path the entry is at in the tree.
219
226
        :param tree: The tree which contains this entry and should be used to 
220
 
        obtain content.
221
 
        :return: True if a new version of the entry has been recorded.
222
 
            (Committing a merge where a file was only changed on the other side
223
 
            will not return True.)
 
227
            obtain content.
 
228
        :param content_summary: Summary data from the tree about the paths
 
229
            content - stat, length, exec, sha/link target. This is only
 
230
            accessed when the entry has a revision of None - that is when it is
 
231
            a candidate to commit.
 
232
        :return: A tuple (change_delta, version_recorded). change_delta is 
 
233
            an inventory_delta change for this entry against the basis tree of
 
234
            the commit, or None if no change occured against the basis tree.
 
235
            version_recorded is True if a new version of the entry has been
 
236
            recorded. For instance, committing a merge where a file was only
 
237
            changed on the other side will return (delta, False).
224
238
        """
225
239
        if self.new_inventory.root is None:
 
240
            if ie.parent_id is not None:
 
241
                raise errors.RootMissing()
226
242
            self._check_root(ie, parent_invs, tree)
 
243
        if ie.revision is None:
 
244
            kind = content_summary[0]
 
245
        else:
 
246
            # ie is carried over from a prior commit
 
247
            kind = ie.kind
 
248
        # XXX: repository specific check for nested tree support goes here - if
 
249
        # the repo doesn't want nested trees we skip it ?
 
250
        if (kind == 'tree-reference' and
 
251
            not self.repository._format.supports_tree_reference):
 
252
            # mismatch between commit builder logic and repository:
 
253
            # this needs the entry creation pushed down into the builder.
 
254
            raise NotImplementedError('Missing repository subtree support.')
 
255
        # transitional assert only, will remove before release.
 
256
        assert ie.kind == kind
227
257
        self.new_inventory.add(ie)
228
258
 
 
259
        # TODO: slow, take it out of the inner loop.
 
260
        try:
 
261
            basis_inv = parent_invs[0]
 
262
        except IndexError:
 
263
            basis_inv = Inventory(root_id=None)
 
264
 
229
265
        # ie.revision is always None if the InventoryEntry is considered
230
 
        # for committing. ie.snapshot will record the correct revision 
231
 
        # which may be the sole parent if it is untouched.
 
266
        # for committing. We may record the previous parents revision if the
 
267
        # content is actually unchanged against a sole head.
232
268
        if ie.revision is not None:
233
 
            return ie.revision == self._new_revision_id and (path != '' or
 
269
            if self._versioned_root or path != '':
 
270
                # not considered for commit
 
271
                delta = None
 
272
            else:
 
273
                # repositories that do not version the root set the root's
 
274
                # revision to the new commit even when no change occurs, and
 
275
                # this masks when a change may have occurred against the basis,
 
276
                # so calculate if one happened.
 
277
                if ie.file_id not in basis_inv:
 
278
                    # add
 
279
                    delta = (None, path, ie.file_id, ie)
 
280
                else:
 
281
                    basis_id = basis_inv[ie.file_id]
 
282
                    if basis_id.name != '':
 
283
                        # not the root
 
284
                        delta = (basis_inv.id2path(ie.file_id), path,
 
285
                            ie.file_id, ie)
 
286
                    else:
 
287
                        # common, unaltered
 
288
                        delta = None
 
289
            # not considered for commit, OR, for non-rich-root 
 
290
            return delta, ie.revision == self._new_revision_id and (path != '' or
234
291
                self._versioned_root)
235
292
 
 
293
        # XXX: Friction: parent_candidates should return a list not a dict
 
294
        #      so that we don't have to walk the inventories again.
236
295
        parent_candiate_entries = ie.parent_candidates(parent_invs)
237
 
        heads = self.repository.get_graph().heads(parent_candiate_entries.keys())
238
 
        # XXX: Note that this is unordered - and this is tolerable because 
239
 
        # the previous code was also unordered.
240
 
        previous_entries = dict((head, parent_candiate_entries[head]) for head
241
 
            in heads)
242
 
        # we are creating a new revision for ie in the history store and
243
 
        # inventory.
244
 
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
245
 
        return ie.revision == self._new_revision_id and (path != '' or
246
 
            self._versioned_root)
247
 
 
248
 
    def modified_directory(self, file_id, file_parents):
249
 
        """Record the presence of a symbolic link.
250
 
 
251
 
        :param file_id: The file_id of the link to record.
252
 
        :param file_parents: The per-file parent revision ids.
253
 
        """
254
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
255
 
 
256
 
    def modified_reference(self, file_id, file_parents):
257
 
        """Record the modification of a reference.
258
 
 
259
 
        :param file_id: The file_id of the link to record.
260
 
        :param file_parents: The per-file parent revision ids.
261
 
        """
262
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
263
 
    
264
 
    def modified_file_text(self, file_id, file_parents,
265
 
                           get_content_byte_lines, text_sha1=None,
266
 
                           text_size=None):
267
 
        """Record the text of file file_id
268
 
 
269
 
        :param file_id: The file_id of the file to record the text of.
270
 
        :param file_parents: The per-file parent revision ids.
271
 
        :param get_content_byte_lines: A callable which will return the byte
272
 
            lines for the file.
273
 
        :param text_sha1: Optional SHA1 of the file contents.
274
 
        :param text_size: Optional size of the file contents.
275
 
        """
276
 
        # mutter('storing text of file {%s} in revision {%s} into %r',
277
 
        #        file_id, self._new_revision_id, self.repository.weave_store)
278
 
        # special case to avoid diffing on renames or 
279
 
        # reparenting
280
 
        if (len(file_parents) == 1
281
 
            and text_sha1 == file_parents.values()[0].text_sha1
282
 
            and text_size == file_parents.values()[0].text_size):
283
 
            previous_ie = file_parents.values()[0]
284
 
            versionedfile = self.repository.weave_store.get_weave(file_id,
285
 
                self.repository.get_transaction())
286
 
            versionedfile.clone_text(self._new_revision_id,
287
 
                previous_ie.revision, file_parents.keys())
288
 
            return text_sha1, text_size
 
296
        head_set = self._repo_graph.heads(parent_candiate_entries.keys())
 
297
        heads = []
 
298
        for inv in parent_invs:
 
299
            if ie.file_id in inv:
 
300
                old_rev = inv[ie.file_id].revision
 
301
                if old_rev in head_set:
 
302
                    heads.append(inv[ie.file_id].revision)
 
303
                    head_set.remove(inv[ie.file_id].revision)
 
304
 
 
305
        store = False
 
306
        # now we check to see if we need to write a new record to the
 
307
        # file-graph.
 
308
        # We write a new entry unless there is one head to the ancestors, and
 
309
        # the kind-derived content is unchanged.
 
310
 
 
311
        # Cheapest check first: no ancestors, or more the one head in the
 
312
        # ancestors, we write a new node.
 
313
        if len(heads) != 1:
 
314
            store = True
 
315
        if not store:
 
316
            # There is a single head, look it up for comparison
 
317
            parent_entry = parent_candiate_entries[heads[0]]
 
318
            # if the non-content specific data has changed, we'll be writing a
 
319
            # node:
 
320
            if (parent_entry.parent_id != ie.parent_id or
 
321
                parent_entry.name != ie.name):
 
322
                store = True
 
323
        # now we need to do content specific checks:
 
324
        if not store:
 
325
            # if the kind changed the content obviously has
 
326
            if kind != parent_entry.kind:
 
327
                store = True
 
328
        if kind == 'file':
 
329
            if not store:
 
330
                if (# if the file length changed we have to store:
 
331
                    parent_entry.text_size != content_summary[1] or
 
332
                    # if the exec bit has changed we have to store:
 
333
                    parent_entry.executable != content_summary[2]):
 
334
                    store = True
 
335
                elif parent_entry.text_sha1 == content_summary[3]:
 
336
                    # all meta and content is unchanged (using a hash cache
 
337
                    # hit to check the sha)
 
338
                    ie.revision = parent_entry.revision
 
339
                    ie.text_size = parent_entry.text_size
 
340
                    ie.text_sha1 = parent_entry.text_sha1
 
341
                    ie.executable = parent_entry.executable
 
342
                    return self._get_delta(ie, basis_inv, path), False
 
343
                else:
 
344
                    # Either there is only a hash change(no hash cache entry,
 
345
                    # or same size content change), or there is no change on
 
346
                    # this file at all.
 
347
                    # Provide the parent's hash to the store layer, so that the
 
348
                    # content is unchanged we will not store a new node.
 
349
                    nostore_sha = parent_entry.text_sha1
 
350
            if store:
 
351
                # We want to record a new node regardless of the presence or
 
352
                # absence of a content change in the file.
 
353
                nostore_sha = None
 
354
            ie.executable = content_summary[2]
 
355
            lines = tree.get_file(ie.file_id, path).readlines()
 
356
            try:
 
357
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
 
358
                    ie.file_id, lines, heads, nostore_sha)
 
359
            except errors.ExistingContent:
 
360
                # Turns out that the file content was unchanged, and we were
 
361
                # only going to store a new node if it was changed. Carry over
 
362
                # the entry.
 
363
                ie.revision = parent_entry.revision
 
364
                ie.text_size = parent_entry.text_size
 
365
                ie.text_sha1 = parent_entry.text_sha1
 
366
                ie.executable = parent_entry.executable
 
367
                return self._get_delta(ie, basis_inv, path), False
 
368
        elif kind == 'directory':
 
369
            if not store:
 
370
                # all data is meta here, nothing specific to directory, so
 
371
                # carry over:
 
372
                ie.revision = parent_entry.revision
 
373
                return self._get_delta(ie, basis_inv, path), False
 
374
            lines = []
 
375
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
376
        elif kind == 'symlink':
 
377
            current_link_target = content_summary[3]
 
378
            if not store:
 
379
                # symlink target is not generic metadata, check if it has
 
380
                # changed.
 
381
                if current_link_target != parent_entry.symlink_target:
 
382
                    store = True
 
383
            if not store:
 
384
                # unchanged, carry over.
 
385
                ie.revision = parent_entry.revision
 
386
                ie.symlink_target = parent_entry.symlink_target
 
387
                return self._get_delta(ie, basis_inv, path), False
 
388
            ie.symlink_target = current_link_target
 
389
            lines = []
 
390
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
391
        elif kind == 'tree-reference':
 
392
            if not store:
 
393
                if content_summary[3] != parent_entry.reference_revision:
 
394
                    store = True
 
395
            if not store:
 
396
                # unchanged, carry over.
 
397
                ie.reference_revision = parent_entry.reference_revision
 
398
                ie.revision = parent_entry.revision
 
399
                return self._get_delta(ie, basis_inv, path), False
 
400
            ie.reference_revision = content_summary[3]
 
401
            lines = []
 
402
            self._add_text_to_weave(ie.file_id, lines, heads, None)
289
403
        else:
290
 
            new_lines = get_content_byte_lines()
291
 
            return self._add_text_to_weave(file_id, new_lines,
292
 
                file_parents.keys())
293
 
 
294
 
    def modified_link(self, file_id, file_parents, link_target):
295
 
        """Record the presence of a symbolic link.
296
 
 
297
 
        :param file_id: The file_id of the link to record.
298
 
        :param file_parents: The per-file parent revision ids.
299
 
        :param link_target: Target location of this link.
300
 
        """
301
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
302
 
 
303
 
    def _add_text_to_weave(self, file_id, new_lines, parents):
 
404
            raise NotImplementedError('unknown kind')
 
405
        ie.revision = self._new_revision_id
 
406
        return self._get_delta(ie, basis_inv, path), True
 
407
 
 
408
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
304
409
        versionedfile = self.repository.weave_store.get_weave_or_empty(
305
410
            file_id, self.repository.get_transaction())
306
411
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
311
416
        # implementation could give us bad output from readlines() so this is
312
417
        # not a guarantee of safety. What would be better is always checking
313
418
        # the content during test suite execution. RBC 20070912
314
 
        result = versionedfile.add_lines_with_ghosts(
315
 
            self._new_revision_id, parents, new_lines,
316
 
            random_id=self.random_revid, check_content=False)[0:2]
317
 
        versionedfile.clear_cache()
318
 
        return result
 
419
        try:
 
420
            return versionedfile.add_lines_with_ghosts(
 
421
                self._new_revision_id, parents, new_lines,
 
422
                nostore_sha=nostore_sha, random_id=self.random_revid,
 
423
                check_content=False)[0:2]
 
424
        finally:
 
425
            versionedfile.clear_cache()
319
426
 
320
427
 
321
428
class RootCommitBuilder(CommitBuilder):
332
439
            commit.
333
440
        :param tree: The tree that is being committed.
334
441
        """
335
 
        # ie must be root for this builder
336
 
        assert ie.parent_id is None
337
442
 
338
443
 
339
444
######################################################################
395
500
 
396
501
        returns the sha1 of the serialized inventory.
397
502
        """
398
 
        revision_id = osutils.safe_revision_id(revision_id)
399
503
        _mod_revision.check_not_reserved_id(revision_id)
400
504
        assert inv.revision_id is None or inv.revision_id == revision_id, \
401
505
            "Mismatch between inventory revision" \
428
532
                       If supplied its signature_needed method will be used
429
533
                       to determine if a signature should be made.
430
534
        """
431
 
        revision_id = osutils.safe_revision_id(revision_id)
432
535
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
433
536
        #       rev.parent_ids?
434
537
        _mod_revision.check_not_reserved_id(revision_id)
525
628
        # on whether escaping is required.
526
629
        self._warn_if_deprecated()
527
630
        self._write_group = None
 
631
        self.base = control_files._transport.base
528
632
 
529
633
    def __repr__(self):
530
 
        return '%s(%r)' % (self.__class__.__name__, 
531
 
                           self.bzrdir.transport.base)
 
634
        return '%s(%r)' % (self.__class__.__name__,
 
635
                           self.base)
532
636
 
533
637
    def has_same_location(self, other):
534
638
        """Returns a boolean indicating if this repository is at the same
659
763
 
660
764
        revision_id: only return revision ids included by revision_id.
661
765
        """
662
 
        revision_id = osutils.safe_revision_id(revision_id)
663
766
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
664
767
 
665
768
    @staticmethod
678
781
        This is a destructive operation! Do not use it on existing 
679
782
        repositories.
680
783
        """
681
 
        revision_id = osutils.safe_revision_id(revision_id)
682
784
        return InterRepository.get(self, destination).copy_content(revision_id)
683
785
 
684
786
    def commit_write_group(self):
688
790
        """
689
791
        if self._write_group is not self.get_transaction():
690
792
            # has an unlock or relock occured ?
691
 
            raise errors.BzrError('mismatched lock context and write group.')
 
793
            raise errors.BzrError('mismatched lock context %r and '
 
794
                'write group %r.' %
 
795
                (self.get_transaction(), self._write_group))
692
796
        self._commit_write_group()
693
797
        self._write_group = None
694
798
 
706
810
 
707
811
        If revision_id is None all content is copied.
708
812
        """
709
 
        revision_id = osutils.safe_revision_id(revision_id)
 
813
        # fast path same-url fetch operations
 
814
        if self.has_same_location(source):
 
815
            # check that last_revision is in 'from' and then return a
 
816
            # no-operation.
 
817
            if (revision_id is not None and
 
818
                not _mod_revision.is_null(revision_id)):
 
819
                self.get_revision(revision_id)
 
820
            return 0, []
710
821
        inter = InterRepository.get(source, self)
711
822
        try:
712
823
            return inter.fetch(revision_id=revision_id, pb=pb)
730
841
        :param revprops: Optional dictionary of revision properties.
731
842
        :param revision_id: Optional revision id.
732
843
        """
733
 
        revision_id = osutils.safe_revision_id(revision_id)
734
844
        result = self._commit_builder_class(self, parents, config,
735
845
            timestamp, timezone, committer, revprops, revision_id)
736
846
        self.start_write_group()
815
925
    def has_revision(self, revision_id):
816
926
        """True if this repository has a copy of the revision."""
817
927
        if 'evil' in debug.debug_flags:
818
 
            mutter_callsite(2, "has_revision is a LBYL symptom.")
819
 
        revision_id = osutils.safe_revision_id(revision_id)
 
928
            mutter_callsite(3, "has_revision is a LBYL symptom.")
820
929
        return self._revision_store.has_revision_id(revision_id,
821
930
                                                    self.get_transaction())
822
931
 
844
953
    @needs_read_lock
845
954
    def _get_revisions(self, revision_ids):
846
955
        """Core work logic to get many revisions without sanity checks."""
847
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
848
956
        for rev_id in revision_ids:
849
957
            if not rev_id or not isinstance(rev_id, basestring):
850
958
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
861
969
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
862
970
        #       would have already do it.
863
971
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
864
 
        revision_id = osutils.safe_revision_id(revision_id)
865
972
        rev = self.get_revision(revision_id)
866
973
        rev_tmp = StringIO()
867
974
        # the current serializer..
902
1009
 
903
1010
    @needs_write_lock
904
1011
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
905
 
        revision_id = osutils.safe_revision_id(revision_id)
906
1012
        signature = gpg_strategy.sign(plaintext)
907
1013
        self._revision_store.add_revision_signature_text(revision_id,
908
1014
                                                         signature,
919
1025
        assert self._serializer.support_altered_by_hack, \
920
1026
            ("fileids_altered_by_revision_ids only supported for branches " 
921
1027
             "which store inventory as unnested xml, not on %r" % self)
922
 
        selected_revision_ids = set(osutils.safe_revision_id(r)
923
 
                                    for r in revision_ids)
 
1028
        selected_revision_ids = set(revision_ids)
924
1029
        w = self.get_inventory_weave()
925
1030
        result = {}
926
1031
 
1068
1173
    @needs_read_lock
1069
1174
    def get_inventory(self, revision_id):
1070
1175
        """Get Inventory object by hash."""
1071
 
        # TODO: jam 20070210 Technically we don't need to sanitize, since all
1072
 
        #       called functions must sanitize.
1073
 
        revision_id = osutils.safe_revision_id(revision_id)
1074
1176
        return self.deserialise_inventory(
1075
1177
            revision_id, self.get_inventory_xml(revision_id))
1076
1178
 
1080
1182
        :param revision_id: The expected revision id of the inventory.
1081
1183
        :param xml: A serialised inventory.
1082
1184
        """
1083
 
        revision_id = osutils.safe_revision_id(revision_id)
1084
 
        result = self._serializer.read_inventory_from_string(xml)
1085
 
        result.root.revision = revision_id
1086
 
        return result
 
1185
        return self._serializer.read_inventory_from_string(xml, revision_id)
1087
1186
 
1088
1187
    def serialise_inventory(self, inv):
1089
1188
        return self._serializer.write_inventory_to_string(inv)
1097
1196
    @needs_read_lock
1098
1197
    def get_inventory_xml(self, revision_id):
1099
1198
        """Get inventory XML as a file object."""
1100
 
        revision_id = osutils.safe_revision_id(revision_id)
1101
1199
        try:
1102
1200
            assert isinstance(revision_id, str), type(revision_id)
1103
1201
            iw = self.get_inventory_weave()
1109
1207
    def get_inventory_sha1(self, revision_id):
1110
1208
        """Return the sha1 hash of the inventory entry
1111
1209
        """
1112
 
        # TODO: jam 20070210 Shouldn't this be deprecated / removed?
1113
 
        revision_id = osutils.safe_revision_id(revision_id)
1114
1210
        return self.get_revision(revision_id).inventory_sha1
1115
1211
 
1116
1212
    @needs_read_lock
1135
1231
        :return: a Graph object with the graph reachable from revision_ids.
1136
1232
        """
1137
1233
        if 'evil' in debug.debug_flags:
1138
 
            mutter_callsite(2,
 
1234
            mutter_callsite(3,
1139
1235
                "get_revision_graph_with_ghosts scales with size of history.")
1140
1236
        result = deprecated_graph.Graph()
1141
1237
        if not revision_ids:
1142
1238
            pending = set(self.all_revision_ids())
1143
1239
            required = set([])
1144
1240
        else:
1145
 
            pending = set(osutils.safe_revision_id(r) for r in revision_ids)
 
1241
            pending = set(revision_ids)
1146
1242
            # special case NULL_REVISION
1147
1243
            if _mod_revision.NULL_REVISION in pending:
1148
1244
                pending.remove(_mod_revision.NULL_REVISION)
1181
1277
        :param revision_id: The revision id to start with.  All its lefthand
1182
1278
            ancestors will be traversed.
1183
1279
        """
1184
 
        revision_id = osutils.safe_revision_id(revision_id)
1185
1280
        if revision_id in (None, _mod_revision.NULL_REVISION):
1186
1281
            return
1187
1282
        next_id = revision_id
1245
1340
            return RevisionTree(self, Inventory(root_id=None), 
1246
1341
                                _mod_revision.NULL_REVISION)
1247
1342
        else:
1248
 
            revision_id = osutils.safe_revision_id(revision_id)
1249
1343
            inv = self.get_revision_inventory(revision_id)
1250
1344
            return RevisionTree(self, inv, revision_id)
1251
1345
 
1273
1367
        """
1274
1368
        if _mod_revision.is_null(revision_id):
1275
1369
            return [None]
1276
 
        revision_id = osutils.safe_revision_id(revision_id)
1277
1370
        if not self.has_revision(revision_id):
1278
1371
            raise errors.NoSuchRevision(self, revision_id)
1279
1372
        w = self.get_inventory_weave()
1299
1392
        - it writes to stdout, it assumes that that is valid etc. Fix
1300
1393
        by creating a new more flexible convenience function.
1301
1394
        """
1302
 
        revision_id = osutils.safe_revision_id(revision_id)
1303
1395
        tree = self.revision_tree(revision_id)
1304
1396
        # use inventory as it was in that revision
1305
1397
        file_id = tree.inventory.path2id(file)
1314
1406
        return self.control_files.get_transaction()
1315
1407
 
1316
1408
    def revision_parents(self, revision_id):
1317
 
        revision_id = osutils.safe_revision_id(revision_id)
1318
1409
        return self.get_inventory_weave().parent_names(revision_id)
1319
1410
 
1320
1411
    def get_parents(self, revision_ids):
1365
1456
 
1366
1457
    @needs_write_lock
1367
1458
    def sign_revision(self, revision_id, gpg_strategy):
1368
 
        revision_id = osutils.safe_revision_id(revision_id)
1369
1459
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1370
1460
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1371
1461
 
1372
1462
    @needs_read_lock
1373
1463
    def has_signature_for_revision_id(self, revision_id):
1374
1464
        """Query for a revision signature for revision_id in the repository."""
1375
 
        revision_id = osutils.safe_revision_id(revision_id)
1376
1465
        return self._revision_store.has_signature(revision_id,
1377
1466
                                                  self.get_transaction())
1378
1467
 
1379
1468
    @needs_read_lock
1380
1469
    def get_signature_text(self, revision_id):
1381
1470
        """Return the text for a signature."""
1382
 
        revision_id = osutils.safe_revision_id(revision_id)
1383
1471
        return self._revision_store.get_signature_text(revision_id,
1384
1472
                                                       self.get_transaction())
1385
1473
 
1395
1483
        if not revision_ids:
1396
1484
            raise ValueError("revision_ids must be non-empty in %s.check" 
1397
1485
                    % (self,))
1398
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1399
1486
        return self._check(revision_ids)
1400
1487
 
1401
1488
    def _check(self, revision_ids):
1556
1643
 
1557
1644
 
1558
1645
class RepositoryFormatRegistry(registry.Registry):
1559
 
    """Registry of RepositoryFormats.
1560
 
    """
 
1646
    """Registry of RepositoryFormats."""
1561
1647
 
1562
1648
    def get(self, format_string):
1563
1649
        r = registry.Registry.get(self, format_string)
1586
1672
       children.
1587
1673
     * an open routine which returns a Repository instance.
1588
1674
 
 
1675
    There is one and only one Format subclass for each on-disk format. But
 
1676
    there can be one Repository subclass that is used for several different
 
1677
    formats. The _format attribute on a Repository instance can be used to
 
1678
    determine the disk format.
 
1679
 
1589
1680
    Formats are placed in an dict by their format string for reference 
1590
1681
    during opening. These should be subclasses of RepositoryFormat
1591
1682
    for consistency.
1787
1878
    'bzrlib.repofmt.weaverepo',
1788
1879
    'RepositoryFormat7'
1789
1880
    )
 
1881
 
1790
1882
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1791
1883
# default control directory format
1792
 
 
1793
1884
format_registry.register_lazy(
1794
1885
    'Bazaar-NG Knit Repository Format 1',
1795
1886
    'bzrlib.repofmt.knitrepo',
1849
1940
        # generic, possibly worst case, slow code path.
1850
1941
        target_ids = set(self.target.all_revision_ids())
1851
1942
        if revision_id is not None:
1852
 
            # TODO: jam 20070210 InterRepository is internal enough that it
1853
 
            #       should assume revision_ids are already utf-8
1854
 
            revision_id = osutils.safe_revision_id(revision_id)
1855
1943
            source_ids = self.source.get_ancestry(revision_id)
1856
1944
            assert source_ids[0] is None
1857
1945
            source_ids.pop(0)
1863
1951
        # that we've decided we need.
1864
1952
        return [rev_id for rev_id in source_ids if rev_id in result_set]
1865
1953
 
 
1954
    @staticmethod
 
1955
    def _same_model(source, target):
 
1956
        """True if source and target have the same data representation."""
 
1957
        if source.supports_rich_root() != target.supports_rich_root():
 
1958
            return False
 
1959
        if source._serializer != target._serializer:
 
1960
            return False
 
1961
        return True
 
1962
 
1866
1963
 
1867
1964
class InterSameDataRepository(InterRepository):
1868
1965
    """Code for converting between repositories that represent the same data.
1882
1979
 
1883
1980
    @staticmethod
1884
1981
    def is_compatible(source, target):
1885
 
        if source.supports_rich_root() != target.supports_rich_root():
1886
 
            return False
1887
 
        if source._serializer != target._serializer:
1888
 
            return False
1889
 
        return True
 
1982
        return InterRepository._same_model(source, target)
1890
1983
 
1891
1984
    @needs_write_lock
1892
1985
    def copy_content(self, revision_id=None):
1905
1998
            self.target.set_make_working_trees(self.source.make_working_trees())
1906
1999
        except NotImplementedError:
1907
2000
            pass
1908
 
        # TODO: jam 20070210 This is fairly internal, so we should probably
1909
 
        #       just assert that revision_id is not unicode.
1910
 
        revision_id = osutils.safe_revision_id(revision_id)
1911
2001
        # but don't bother fetching if we have the needed data now.
1912
2002
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
1913
2003
            self.target.has_revision(revision_id)):
1919
2009
        """See InterRepository.fetch()."""
1920
2010
        from bzrlib.fetch import GenericRepoFetcher
1921
2011
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1922
 
               self.source, self.source._format, self.target, 
 
2012
               self.source, self.source._format, self.target,
1923
2013
               self.target._format)
1924
 
        # TODO: jam 20070210 This should be an assert, not a translate
1925
 
        revision_id = osutils.safe_revision_id(revision_id)
1926
2014
        f = GenericRepoFetcher(to_repository=self.target,
1927
2015
                               from_repository=self.source,
1928
2016
                               last_revision=revision_id,
1969
2057
    def copy_content(self, revision_id=None):
1970
2058
        """See InterRepository.copy_content()."""
1971
2059
        # weave specific optimised path:
1972
 
        # TODO: jam 20070210 Internal, should be an assert, not translate
1973
 
        revision_id = osutils.safe_revision_id(revision_id)
1974
2060
        try:
1975
2061
            self.target.set_make_working_trees(self.source.make_working_trees())
1976
2062
        except NotImplementedError:
2003
2089
        from bzrlib.fetch import GenericRepoFetcher
2004
2090
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2005
2091
               self.source, self.source._format, self.target, self.target._format)
2006
 
        # TODO: jam 20070210 This should be an assert, not a translate
2007
 
        revision_id = osutils.safe_revision_id(revision_id)
2008
2092
        f = GenericRepoFetcher(to_repository=self.target,
2009
2093
                               from_repository=self.source,
2010
2094
                               last_revision=revision_id,
2069
2153
        could lead to confusing results, and there is no need to be 
2070
2154
        overly general.
2071
2155
        """
2072
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
 
2156
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
2073
2157
        try:
2074
 
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
2075
 
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
2158
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
2159
                isinstance(target._format, RepositoryFormatKnit))
2076
2160
        except AttributeError:
2077
2161
            return False
 
2162
        return are_knits and InterRepository._same_model(source, target)
2078
2163
 
2079
2164
    @needs_write_lock
2080
2165
    def fetch(self, revision_id=None, pb=None):
2082
2167
        from bzrlib.fetch import KnitRepoFetcher
2083
2168
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2084
2169
               self.source, self.source._format, self.target, self.target._format)
2085
 
        # TODO: jam 20070210 This should be an assert, not a translate
2086
 
        revision_id = osutils.safe_revision_id(revision_id)
2087
2170
        f = KnitRepoFetcher(to_repository=self.target,
2088
2171
                            from_repository=self.source,
2089
2172
                            last_revision=revision_id,
2138
2221
    def fetch(self, revision_id=None, pb=None):
2139
2222
        """See InterRepository.fetch()."""
2140
2223
        from bzrlib.fetch import Model1toKnit2Fetcher
2141
 
        # TODO: jam 20070210 This should be an assert, not a translate
2142
 
        revision_id = osutils.safe_revision_id(revision_id)
2143
2224
        f = Model1toKnit2Fetcher(to_repository=self.target,
2144
2225
                                 from_repository=self.source,
2145
2226
                                 last_revision=revision_id,
2160
2241
            self.target.set_make_working_trees(self.source.make_working_trees())
2161
2242
        except NotImplementedError:
2162
2243
            pass
2163
 
        # TODO: jam 20070210 Internal, assert, don't translate
2164
 
        revision_id = osutils.safe_revision_id(revision_id)
2165
2244
        # but don't bother fetching if we have the needed data now.
2166
2245
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
2167
2246
            self.target.has_revision(revision_id)):
2194
2273
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2195
2274
               self.source, self.source._format, self.target, 
2196
2275
               self.target._format)
2197
 
        # TODO: jam 20070210 This should be an assert, not a translate
2198
 
        revision_id = osutils.safe_revision_id(revision_id)
2199
2276
        f = Knit1to2Fetcher(to_repository=self.target,
2200
2277
                            from_repository=self.source,
2201
2278
                            last_revision=revision_id,