/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2007-10-08 07:29:57 UTC
  • mfrom: (2894 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2895.
  • Revision ID: mbp@sourcefrog.net-20071008072957-uhm1gl1mqcsdc377
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
######################################################################
523
628
        # on whether escaping is required.
524
629
        self._warn_if_deprecated()
525
630
        self._write_group = None
 
631
        self.base = control_files._transport.base
526
632
 
527
633
    def __repr__(self):
528
 
        return '%s(%r)' % (self.__class__.__name__, 
529
 
                           self.bzrdir.transport.base)
 
634
        return '%s(%r)' % (self.__class__.__name__,
 
635
                           self.base)
530
636
 
531
637
    def has_same_location(self, other):
532
638
        """Returns a boolean indicating if this repository is at the same
684
790
        """
685
791
        if self._write_group is not self.get_transaction():
686
792
            # has an unlock or relock occured ?
687
 
            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))
688
796
        self._commit_write_group()
689
797
        self._write_group = None
690
798
 
702
810
 
703
811
        If revision_id is None all content is copied.
704
812
        """
 
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, []
705
821
        inter = InterRepository.get(source, self)
706
822
        try:
707
823
            return inter.fetch(revision_id=revision_id, pb=pb)
809
925
    def has_revision(self, revision_id):
810
926
        """True if this repository has a copy of the revision."""
811
927
        if 'evil' in debug.debug_flags:
812
 
            mutter_callsite(2, "has_revision is a LBYL symptom.")
 
928
            mutter_callsite(3, "has_revision is a LBYL symptom.")
813
929
        return self._revision_store.has_revision_id(revision_id,
814
930
                                                    self.get_transaction())
815
931
 
1117
1233
        :return: a Graph object with the graph reachable from revision_ids.
1118
1234
        """
1119
1235
        if 'evil' in debug.debug_flags:
1120
 
            mutter_callsite(2,
 
1236
            mutter_callsite(3,
1121
1237
                "get_revision_graph_with_ghosts scales with size of history.")
1122
1238
        result = deprecated_graph.Graph()
1123
1239
        if not revision_ids:
1760
1876
    'bzrlib.repofmt.weaverepo',
1761
1877
    'RepositoryFormat7'
1762
1878
    )
 
1879
 
1763
1880
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1764
1881
# default control directory format
1765
 
 
1766
1882
format_registry.register_lazy(
1767
1883
    'Bazaar-NG Knit Repository Format 1',
1768
1884
    'bzrlib.repofmt.knitrepo',
1833
1949
        # that we've decided we need.
1834
1950
        return [rev_id for rev_id in source_ids if rev_id in result_set]
1835
1951
 
 
1952
    @staticmethod
 
1953
    def _same_model(source, target):
 
1954
        """True if source and target have the same data representation."""
 
1955
        if source.supports_rich_root() != target.supports_rich_root():
 
1956
            return False
 
1957
        if source._serializer != target._serializer:
 
1958
            return False
 
1959
        return True
 
1960
 
1836
1961
 
1837
1962
class InterSameDataRepository(InterRepository):
1838
1963
    """Code for converting between repositories that represent the same data.
1852
1977
 
1853
1978
    @staticmethod
1854
1979
    def is_compatible(source, target):
1855
 
        if source.supports_rich_root() != target.supports_rich_root():
1856
 
            return False
1857
 
        if source._serializer != target._serializer:
1858
 
            return False
1859
 
        return True
 
1980
        return InterRepository._same_model(source, target)
1860
1981
 
1861
1982
    @needs_write_lock
1862
1983
    def copy_content(self, revision_id=None):
2030
2151
        could lead to confusing results, and there is no need to be 
2031
2152
        overly general.
2032
2153
        """
2033
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
 
2154
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
2034
2155
        try:
2035
 
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
2036
 
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
2156
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
2157
                isinstance(target._format, RepositoryFormatKnit))
2037
2158
        except AttributeError:
2038
2159
            return False
 
2160
        return are_knits and InterRepository._same_model(source, target)
2039
2161
 
2040
2162
    @needs_write_lock
2041
2163
    def fetch(self, revision_id=None, pb=None):