/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: Aaron Bentley
  • Date: 2009-03-24 12:15:01 UTC
  • mfrom: (4195 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4196.
  • Revision ID: aaron@aaronbentley.com-20090324121501-rd8zjsdk9fok8mvk
Merge and resolve NEWS conflicts

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, 2008, 2009 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from bzrlib.lazy_import import lazy_import
18
18
lazy_import(globals(), """
34
34
    lockdir,
35
35
    lru_cache,
36
36
    osutils,
37
 
    remote,
38
37
    revision as _mod_revision,
39
38
    symbol_versioning,
40
39
    tsort,
49
48
 
50
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
51
50
from bzrlib.inter import InterObject
52
 
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
51
from bzrlib.inventory import (
 
52
    Inventory,
 
53
    InventoryDirectory,
 
54
    ROOT_ID,
 
55
    entry_factory,
 
56
    )
53
57
from bzrlib import registry
54
58
from bzrlib.symbol_versioning import (
55
59
        deprecated_method,
56
 
        one_one,
57
 
        one_two,
58
 
        one_six,
59
60
        )
60
61
from bzrlib.trace import (
61
62
    log_exception_quietly, note, mutter, mutter_callsite, warning)
126
127
        # valid. Callers that will call record_delete() should call
127
128
        # .will_record_deletes() to indicate that.
128
129
        self._recording_deletes = False
 
130
        # memo'd check for no-op commits.
 
131
        self._any_changes = False
 
132
 
 
133
    def any_changes(self):
 
134
        """Return True if any entries were changed.
 
135
        
 
136
        This includes merge-only changes. It is the core for the --unchanged
 
137
        detection in commit.
 
138
 
 
139
        :return: True if any changes have occured.
 
140
        """
 
141
        return self._any_changes
129
142
 
130
143
    def _validate_unicode_text(self, text, context):
131
144
        """Verify things like commit messages don't have bogus characters."""
176
189
        deserializing the inventory, while we already have a copy in
177
190
        memory.
178
191
        """
 
192
        if self.new_inventory is None:
 
193
            self.new_inventory = self.repository.get_inventory(
 
194
                self._new_revision_id)
179
195
        return RevisionTree(self.repository, self.new_inventory,
180
 
                            self._new_revision_id)
 
196
            self._new_revision_id)
181
197
 
182
198
    def finish_inventory(self):
183
 
        """Tell the builder that the inventory is finished."""
184
 
        if self.new_inventory.root is None:
185
 
            raise AssertionError('Root entry should be supplied to'
186
 
                ' record_entry_contents, as of bzr 0.10.')
187
 
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
188
 
        self.new_inventory.revision_id = self._new_revision_id
189
 
        self.inv_sha1 = self.repository.add_inventory(
190
 
            self._new_revision_id,
191
 
            self.new_inventory,
192
 
            self.parents
193
 
            )
 
199
        """Tell the builder that the inventory is finished.
 
200
        
 
201
        :return: The inventory id in the repository, which can be used with
 
202
            repository.get_inventory.
 
203
        """
 
204
        if self.new_inventory is None:
 
205
            # an inventory delta was accumulated without creating a new
 
206
            # inventory.
 
207
            basis_id = self.basis_delta_revision
 
208
            self.inv_sha1 = self.repository.add_inventory_by_delta(
 
209
                basis_id, self._basis_delta, self._new_revision_id,
 
210
                self.parents)
 
211
        else:
 
212
            if self.new_inventory.root is None:
 
213
                raise AssertionError('Root entry should be supplied to'
 
214
                    ' record_entry_contents, as of bzr 0.10.')
 
215
                self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
216
            self.new_inventory.revision_id = self._new_revision_id
 
217
            self.inv_sha1 = self.repository.add_inventory(
 
218
                self._new_revision_id,
 
219
                self.new_inventory,
 
220
                self.parents
 
221
                )
 
222
        return self._new_revision_id
194
223
 
195
224
    def _gen_revision_id(self):
196
225
        """Return new revision-id."""
233
262
        # _new_revision_id
234
263
        ie.revision = self._new_revision_id
235
264
 
 
265
    def _require_root_change(self, tree):
 
266
        """Enforce an appropriate root object change.
 
267
 
 
268
        This is called once when record_iter_changes is called, if and only if
 
269
        the root was not in the delta calculated by record_iter_changes.
 
270
 
 
271
        :param tree: The tree which is being committed.
 
272
        """
 
273
        # NB: if there are no parents then this method is not called, so no
 
274
        # need to guard on parents having length.
 
275
        entry = entry_factory['directory'](tree.path2id(''), '',
 
276
            None)
 
277
        entry.revision = self._new_revision_id
 
278
        self._basis_delta.append(('', '', entry.file_id, entry))
 
279
 
236
280
    def _get_delta(self, ie, basis_inv, path):
237
281
        """Get a delta against the basis inventory for ie."""
238
282
        if ie.file_id not in basis_inv:
290
334
        builder.record_delete().
291
335
        """
292
336
        self._recording_deletes = True
 
337
        try:
 
338
            basis_id = self.parents[0]
 
339
        except IndexError:
 
340
            basis_id = _mod_revision.NULL_REVISION
 
341
        self.basis_delta_revision = basis_id
293
342
 
294
343
    def record_entry_contents(self, ie, parent_invs, path, tree,
295
344
        content_summary):
497
546
        else:
498
547
            raise NotImplementedError('unknown kind')
499
548
        ie.revision = self._new_revision_id
 
549
        self._any_changes = True
500
550
        return self._get_delta(ie, basis_inv, path), True, fingerprint
501
551
 
 
552
    def record_iter_changes(self, tree, basis_revision_id, iter_changes,
 
553
        _entry_factory=entry_factory):
 
554
        """Record a new tree via iter_changes.
 
555
 
 
556
        :param tree: The tree to obtain text contents from for changed objects.
 
557
        :param basis_revision_id: The revision id of the tree the iter_changes
 
558
            has been generated against. Currently assumed to be the same
 
559
            as self.parents[0] - if it is not, errors may occur.
 
560
        :param iter_changes: An iter_changes iterator with the changes to apply
 
561
            to basis_revision_id.
 
562
        :param _entry_factory: Private method to bind entry_factory locally for
 
563
            performance.
 
564
        :return: None
 
565
        """
 
566
        # Create an inventory delta based on deltas between all the parents and
 
567
        # deltas between all the parent inventories. We use inventory delta's 
 
568
        # between the inventory objects because iter_changes masks
 
569
        # last-changed-field only changes.
 
570
        # Working data:
 
571
        # file_id -> change map, change is fileid, paths, changed, versioneds,
 
572
        # parents, names, kinds, executables
 
573
        merged_ids = {}
 
574
        # {file_id -> revision_id -> inventory entry, for entries in parent
 
575
        # trees that are not parents[0]
 
576
        parent_entries = {}
 
577
        revtrees = list(self.repository.revision_trees(self.parents))
 
578
        # The basis inventory from a repository 
 
579
        if revtrees:
 
580
            basis_inv = revtrees[0].inventory
 
581
        else:
 
582
            basis_inv = self.repository.revision_tree(
 
583
                _mod_revision.NULL_REVISION).inventory
 
584
        if len(self.parents) > 0:
 
585
            if basis_revision_id != self.parents[0]:
 
586
                raise Exception(
 
587
                    "arbitrary basis parents not yet supported with merges")
 
588
            for revtree in revtrees[1:]:
 
589
                for change in revtree.inventory._make_delta(basis_inv):
 
590
                    if change[1] is None:
 
591
                        # Not present in this parent.
 
592
                        continue
 
593
                    if change[2] not in merged_ids:
 
594
                        if change[0] is not None:
 
595
                            merged_ids[change[2]] = [
 
596
                                basis_inv[change[2]].revision,
 
597
                                change[3].revision]
 
598
                        else:
 
599
                            merged_ids[change[2]] = [change[3].revision]
 
600
                        parent_entries[change[2]] = {change[3].revision:change[3]}
 
601
                    else:
 
602
                        merged_ids[change[2]].append(change[3].revision)
 
603
                        parent_entries[change[2]][change[3].revision] = change[3]
 
604
        else:
 
605
            merged_ids = {}
 
606
        # Setup the changes from the tree:
 
607
        # changes maps file_id -> (change, [parent revision_ids])
 
608
        changes= {}
 
609
        for change in iter_changes:
 
610
            # This probably looks up in basis_inv way to much.
 
611
            if change[1][0] is not None:
 
612
                head_candidate = [basis_inv[change[0]].revision]
 
613
            else:
 
614
                head_candidate = []
 
615
            changes[change[0]] = change, merged_ids.get(change[0],
 
616
                head_candidate)
 
617
        unchanged_merged = set(merged_ids) - set(changes)
 
618
        # Extend the changes dict with synthetic changes to record merges of
 
619
        # texts.
 
620
        for file_id in unchanged_merged:
 
621
            # Record a merged version of these items that did not change vs the
 
622
            # basis. This can be either identical parallel changes, or a revert
 
623
            # of a specific file after a merge. The recorded content will be
 
624
            # that of the current tree (which is the same as the basis), but
 
625
            # the per-file graph will reflect a merge.
 
626
            # NB:XXX: We are reconstructing path information we had, this
 
627
            # should be preserved instead.
 
628
            # inv delta  change: (file_id, (path_in_source, path_in_target),
 
629
            #   changed_content, versioned, parent, name, kind,
 
630
            #   executable)
 
631
            basis_entry = basis_inv[file_id]
 
632
            change = (file_id,
 
633
                (basis_inv.id2path(file_id), tree.id2path(file_id)),
 
634
                False, (True, True),
 
635
                (basis_entry.parent_id, basis_entry.parent_id),
 
636
                (basis_entry.name, basis_entry.name),
 
637
                (basis_entry.kind, basis_entry.kind),
 
638
                (basis_entry.executable, basis_entry.executable))
 
639
            changes[file_id] = (change, merged_ids[file_id])
 
640
        # changes contains tuples with the change and a set of inventory
 
641
        # candidates for the file.
 
642
        # inv delta is:
 
643
        # old_path, new_path, file_id, new_inventory_entry
 
644
        seen_root = False # Is the root in the basis delta?
 
645
        inv_delta = self._basis_delta
 
646
        modified_rev = self._new_revision_id
 
647
        for change, head_candidates in changes.values():
 
648
            if change[3][1]: # versioned in target.
 
649
                # Several things may be happening here:
 
650
                # We may have a fork in the per-file graph
 
651
                #  - record a change with the content from tree
 
652
                # We may have a change against < all trees  
 
653
                #  - carry over the tree that hasn't changed
 
654
                # We may have a change against all trees
 
655
                #  - record the change with the content from tree
 
656
                kind = change[6][1]
 
657
                file_id = change[0]
 
658
                entry = _entry_factory[kind](file_id, change[5][1],
 
659
                    change[4][1])
 
660
                head_set = self._heads(change[0], set(head_candidates))
 
661
                heads = []
 
662
                # Preserve ordering.
 
663
                for head_candidate in head_candidates:
 
664
                    if head_candidate in head_set:
 
665
                        heads.append(head_candidate)
 
666
                        head_set.remove(head_candidate)
 
667
                carried_over = False
 
668
                if len(heads) == 1:
 
669
                    # Could be a carry-over situation:
 
670
                    parent_entry_revs = parent_entries.get(file_id, None)
 
671
                    if parent_entry_revs:
 
672
                        parent_entry = parent_entry_revs.get(heads[0], None)
 
673
                    else:
 
674
                        parent_entry = None
 
675
                    if parent_entry is None:
 
676
                        # The parent iter_changes was called against is the one
 
677
                        # that is the per-file head, so any change is relevant
 
678
                        # iter_changes is valid.
 
679
                        carry_over_possible = False
 
680
                    else:
 
681
                        # could be a carry over situation
 
682
                        # A change against the basis may just indicate a merge,
 
683
                        # we need to check the content against the source of the
 
684
                        # merge to determine if it was changed after the merge
 
685
                        # or carried over.
 
686
                        if (parent_entry.kind != entry.kind or
 
687
                            parent_entry.parent_id != entry.parent_id or
 
688
                            parent_entry.name != entry.name):
 
689
                            # Metadata common to all entries has changed
 
690
                            # against per-file parent
 
691
                            carry_over_possible = False
 
692
                        else:
 
693
                            carry_over_possible = True
 
694
                        # per-type checks for changes against the parent_entry
 
695
                        # are done below.
 
696
                else:
 
697
                    # Cannot be a carry-over situation
 
698
                    carry_over_possible = False
 
699
                # Populate the entry in the delta
 
700
                if kind == 'file':
 
701
                    # XXX: There is still a small race here: If someone reverts the content of a file
 
702
                    # after iter_changes examines and decides it has changed,
 
703
                    # we will unconditionally record a new version even if some
 
704
                    # other process reverts it while commit is running (with
 
705
                    # the revert happening after iter_changes did it's
 
706
                    # examination).
 
707
                    if change[7][1]:
 
708
                        entry.executable = True
 
709
                    else:
 
710
                        entry.executable = False
 
711
                    if (carry_over_possible and 
 
712
                        parent_entry.executable == entry.executable):
 
713
                            # Check the file length, content hash after reading
 
714
                            # the file.
 
715
                            nostore_sha = parent_entry.text_sha1
 
716
                    else:
 
717
                        nostore_sha = None
 
718
                    file_obj, stat_value = tree.get_file_with_stat(file_id, change[1][1])
 
719
                    try:
 
720
                        lines = file_obj.readlines()
 
721
                    finally:
 
722
                        file_obj.close()
 
723
                    try:
 
724
                        entry.text_sha1, entry.text_size = self._add_text_to_weave(
 
725
                            file_id, lines, heads, nostore_sha)
 
726
                    except errors.ExistingContent:
 
727
                        # No content change against a carry_over parent
 
728
                        carried_over = True
 
729
                        entry.text_size = parent_entry.text_size
 
730
                        entry.text_sha1 = parent_entry.text_sha1
 
731
                elif kind == 'symlink':
 
732
                    # Wants a path hint?
 
733
                    entry.symlink_target = tree.get_symlink_target(file_id)
 
734
                    if (carry_over_possible and
 
735
                        parent_entry.symlink_target == entry.symlink_target):
 
736
                            carried_over = True
 
737
                    else:
 
738
                        self._add_text_to_weave(change[0], [], heads, None)
 
739
                elif kind == 'directory':
 
740
                    if carry_over_possible:
 
741
                        carried_over = True
 
742
                    else:
 
743
                        # Nothing to set on the entry.
 
744
                        # XXX: split into the Root and nonRoot versions.
 
745
                        if change[1][1] != '' or self.repository.supports_rich_root():
 
746
                            self._add_text_to_weave(change[0], [], heads, None)
 
747
                elif kind == 'tree-reference':
 
748
                    raise AssertionError('unknown kind %r' % kind)
 
749
                else:
 
750
                    raise AssertionError('unknown kind %r' % kind)
 
751
                if not carried_over:
 
752
                    entry.revision = modified_rev
 
753
                else:
 
754
                    entry.revision = parent_entry.revision
 
755
            else:
 
756
                entry = None
 
757
            new_path = change[1][1]
 
758
            inv_delta.append((change[1][0], new_path, change[0], entry))
 
759
            if new_path == '':
 
760
                seen_root = True
 
761
        self.new_inventory = None
 
762
        if len(inv_delta):
 
763
            self._any_changes = True
 
764
        if not seen_root:
 
765
            # housekeeping root entry changes do not affect no-change commits.
 
766
            self._require_root_change(tree)
 
767
        self.basis_delta_revision = basis_revision_id
 
768
 
502
769
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
503
770
        # Note: as we read the content directly from the tree, we know its not
504
771
        # been turned into unicode or badly split - but a broken tree
527
794
        :param tree: The tree that is being committed.
528
795
        """
529
796
 
 
797
    def _require_root_change(self, tree):
 
798
        """Enforce an appropriate root object change.
 
799
 
 
800
        This is called once when record_iter_changes is called, if and only if
 
801
        the root was not in the delta calculated by record_iter_changes.
 
802
 
 
803
        :param tree: The tree which is being committed.
 
804
        """
 
805
        # versioned roots do not change unless the tree found a change.
 
806
 
530
807
 
531
808
######################################################################
532
809
# Repositories
884
1161
 
885
1162
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
886
1163
        """
 
1164
        locked = self.is_locked()
887
1165
        result = self.control_files.lock_write(token=token)
888
1166
        for repo in self._fallback_repositories:
889
1167
            # Writes don't affect fallback repos
890
1168
            repo.lock_read()
891
 
        self._refresh_data()
 
1169
        if not locked:
 
1170
            self._refresh_data()
892
1171
        return result
893
1172
 
894
1173
    def lock_read(self):
 
1174
        locked = self.is_locked()
895
1175
        self.control_files.lock_read()
896
1176
        for repo in self._fallback_repositories:
897
1177
            repo.lock_read()
898
 
        self._refresh_data()
 
1178
        if not locked:
 
1179
            self._refresh_data()
899
1180
 
900
1181
    def get_physical_lock_status(self):
901
1182
        return self.control_files.get_physical_lock_status()
1023
1304
        return InterRepository.get(other, self).search_missing_revision_ids(
1024
1305
            revision_id, find_ghosts)
1025
1306
 
1026
 
    @deprecated_method(one_two)
1027
 
    @needs_read_lock
1028
 
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1029
 
        """Return the revision ids that other has that this does not.
1030
 
 
1031
 
        These are returned in topological order.
1032
 
 
1033
 
        revision_id: only return revision ids included by revision_id.
1034
 
        """
1035
 
        keys =  self.search_missing_revision_ids(
1036
 
            other, revision_id, find_ghosts).get_keys()
1037
 
        other.lock_read()
1038
 
        try:
1039
 
            parents = other.get_graph().get_parent_map(keys)
1040
 
        finally:
1041
 
            other.unlock()
1042
 
        return tsort.topo_sort(parents)
1043
 
 
1044
1307
    @staticmethod
1045
1308
    def open(base):
1046
1309
        """Open the repository rooted at base.
1084
1347
    def suspend_write_group(self):
1085
1348
        raise errors.UnsuspendableWriteGroup(self)
1086
1349
 
 
1350
    def refresh_data(self):
 
1351
        """Re-read any data needed to to synchronise with disk.
 
1352
 
 
1353
        This method is intended to be called after another repository instance
 
1354
        (such as one used by a smart server) has inserted data into the
 
1355
        repository. It may not be called during a write group, but may be
 
1356
        called at any other time.
 
1357
        """
 
1358
        if self.is_in_write_group():
 
1359
            raise errors.InternalBzrError(
 
1360
                "May not refresh_data while in a write group.")
 
1361
        self._refresh_data()
 
1362
 
1087
1363
    def resume_write_group(self, tokens):
1088
1364
        if not self.is_write_locked():
1089
1365
            raise errors.NotWriteLocked(self)
1103
1379
        If revision_id is None and fetch_spec is None, then all content is
1104
1380
        copied.
1105
1381
 
 
1382
        fetch() may not be used when the repository is in a write group -
 
1383
        either finish the current write group before using fetch, or use
 
1384
        fetch before starting the write group.
 
1385
 
1106
1386
        :param find_ghosts: Find and copy revisions in the source that are
1107
1387
            ghosts in the target (and not reachable directly by walking out to
1108
1388
            the first-present revision in target from revision_id).
1117
1397
        if fetch_spec is not None and revision_id is not None:
1118
1398
            raise AssertionError(
1119
1399
                "fetch_spec and revision_id are mutually exclusive.")
 
1400
        if self.is_in_write_group():
 
1401
            raise errors.InternalBzrError(
 
1402
                "May not fetch while in a write group.")
1120
1403
        # fast path same-url fetch operations
1121
1404
        if self.has_same_location(source) and fetch_spec is None:
1122
1405
            # check that last_revision is in 'from' and then return a
1313
1596
        rev_tmp.seek(0)
1314
1597
        return rev_tmp.getvalue()
1315
1598
 
1316
 
    def get_deltas_for_revisions(self, revisions):
 
1599
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1317
1600
        """Produce a generator of revision deltas.
1318
1601
 
1319
1602
        Note that the input is a sequence of REVISIONS, not revision_ids.
1320
1603
        Trees will be held in memory until the generator exits.
1321
1604
        Each delta is relative to the revision's lefthand predecessor.
 
1605
 
 
1606
        :param specific_fileids: if not None, the result is filtered
 
1607
          so that only those file-ids, their parents and their
 
1608
          children are included.
1322
1609
        """
 
1610
        # Get the revision-ids of interest
1323
1611
        required_trees = set()
1324
1612
        for revision in revisions:
1325
1613
            required_trees.add(revision.revision_id)
1326
1614
            required_trees.update(revision.parent_ids[:1])
1327
 
        trees = dict((t.get_revision_id(), t) for
1328
 
                     t in self.revision_trees(required_trees))
 
1615
 
 
1616
        # Get the matching filtered trees. Note that it's more
 
1617
        # efficient to pass filtered trees to changes_from() rather
 
1618
        # than doing the filtering afterwards. changes_from() could
 
1619
        # arguably do the filtering itself but it's path-based, not
 
1620
        # file-id based, so filtering before or afterwards is
 
1621
        # currently easier.
 
1622
        if specific_fileids is None:
 
1623
            trees = dict((t.get_revision_id(), t) for
 
1624
                t in self.revision_trees(required_trees))
 
1625
        else:
 
1626
            trees = dict((t.get_revision_id(), t) for
 
1627
                t in self._filtered_revision_trees(required_trees,
 
1628
                specific_fileids))
 
1629
 
 
1630
        # Calculate the deltas
1329
1631
        for revision in revisions:
1330
1632
            if not revision.parent_ids:
1331
1633
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
1334
1636
            yield trees[revision.revision_id].changes_from(old_tree)
1335
1637
 
1336
1638
    @needs_read_lock
1337
 
    def get_revision_delta(self, revision_id):
 
1639
    def get_revision_delta(self, revision_id, specific_fileids=None):
1338
1640
        """Return the delta for one revision.
1339
1641
 
1340
1642
        The delta is relative to the left-hand predecessor of the
1341
1643
        revision.
 
1644
 
 
1645
        :param specific_fileids: if not None, the result is filtered
 
1646
          so that only those file-ids, their parents and their
 
1647
          children are included.
1342
1648
        """
1343
1649
        r = self.get_revision(revision_id)
1344
 
        return list(self.get_deltas_for_revisions([r]))[0]
 
1650
        return list(self.get_deltas_for_revisions([r],
 
1651
            specific_fileids=specific_fileids))[0]
1345
1652
 
1346
1653
    @needs_write_lock
1347
1654
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1356
1663
    def find_text_key_references(self):
1357
1664
        """Find the text key references within the repository.
1358
1665
 
1359
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1360
 
        revision_ids. Each altered file-ids has the exact revision_ids that
1361
 
        altered it listed explicitly.
1362
1666
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1363
1667
            to whether they were referred to by the inventory of the
1364
1668
            revision_id that they contain. The inventory texts from all present
1838
2142
        for repositories to maintain loaded indices across multiple locks
1839
2143
        by checking inside their implementation of this method to see
1840
2144
        whether their indices are still valid. This depends of course on
1841
 
        the disk format being validatable in this manner.
 
2145
        the disk format being validatable in this manner. This method is
 
2146
        also called by the refresh_data() public interface to cause a refresh
 
2147
        to occur while in a write lock so that data inserted by a smart server
 
2148
        push operation is visible on the client's instance of the physical
 
2149
        repository.
1842
2150
        """
1843
2151
 
1844
2152
    @needs_read_lock
1858
2166
            return RevisionTree(self, inv, revision_id)
1859
2167
 
1860
2168
    def revision_trees(self, revision_ids):
1861
 
        """Return Tree for a revision on this branch.
 
2169
        """Return Trees for revisions in this repository.
1862
2170
 
1863
 
        `revision_id` may not be None or 'null:'"""
 
2171
        :param revision_ids: a sequence of revision-ids;
 
2172
          a revision-id may not be None or 'null:'
 
2173
        """
1864
2174
        inventories = self.iter_inventories(revision_ids)
1865
2175
        for inv in inventories:
1866
2176
            yield RevisionTree(self, inv, inv.revision_id)
1867
2177
 
 
2178
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2179
        """Return Tree for a revision on this branch with only some files.
 
2180
 
 
2181
        :param revision_ids: a sequence of revision-ids;
 
2182
          a revision-id may not be None or 'null:'
 
2183
        :param file_ids: if not None, the result is filtered
 
2184
          so that only those file-ids, their parents and their
 
2185
          children are included.
 
2186
        """
 
2187
        inventories = self.iter_inventories(revision_ids)
 
2188
        for inv in inventories:
 
2189
            # Should we introduce a FilteredRevisionTree class rather
 
2190
            # than pre-filter the inventory here?
 
2191
            filtered_inv = inv.filter(file_ids)
 
2192
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2193
 
1868
2194
    @needs_read_lock
1869
2195
    def get_ancestry(self, revision_id, topo_sorted=True):
1870
2196
        """Return a list of revision-ids integrated by a revision.
1906
2232
        implicitly lock for the user.
1907
2233
        """
1908
2234
 
1909
 
    @needs_read_lock
1910
 
    @deprecated_method(one_six)
1911
 
    def print_file(self, file, revision_id):
1912
 
        """Print `file` to stdout.
1913
 
 
1914
 
        FIXME RBC 20060125 as John Meinel points out this is a bad api
1915
 
        - it writes to stdout, it assumes that that is valid etc. Fix
1916
 
        by creating a new more flexible convenience function.
1917
 
        """
1918
 
        tree = self.revision_tree(revision_id)
1919
 
        # use inventory as it was in that revision
1920
 
        file_id = tree.inventory.path2id(file)
1921
 
        if not file_id:
1922
 
            # TODO: jam 20060427 Write a test for this code path
1923
 
            #       it had a bug in it, and was raising the wrong
1924
 
            #       exception.
1925
 
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
1926
 
        tree.print_file(file_id)
1927
 
 
1928
2235
    def get_transaction(self):
1929
2236
        return self.control_files.get_transaction()
1930
2237
 
1931
 
    @deprecated_method(one_one)
1932
 
    def get_parents(self, revision_ids):
1933
 
        """See StackedParentsProvider.get_parents"""
1934
 
        parent_map = self.get_parent_map(revision_ids)
1935
 
        return [parent_map.get(r, None) for r in revision_ids]
1936
 
 
1937
2238
    def get_parent_map(self, revision_ids):
1938
2239
        """See graph._StackedParentsProvider.get_parent_map"""
1939
2240
        # revisions index works in keys; this just works in revisions
1968
2269
                [parents_provider, other_repository._make_parents_provider()])
1969
2270
        return graph.Graph(parents_provider)
1970
2271
 
1971
 
    def _get_versioned_file_checker(self):
1972
 
        """Return an object suitable for checking versioned files."""
1973
 
        return _VersionedFileChecker(self)
 
2272
    def _get_versioned_file_checker(self, text_key_references=None):
 
2273
        """Return an object suitable for checking versioned files.
 
2274
        
 
2275
        :param text_key_references: if non-None, an already built
 
2276
            dictionary mapping text keys ((fileid, revision_id) tuples)
 
2277
            to whether they were referred to by the inventory of the
 
2278
            revision_id that they contain. If None, this will be
 
2279
            calculated.
 
2280
        """
 
2281
        return _VersionedFileChecker(self,
 
2282
            text_key_references=text_key_references)
1974
2283
 
1975
2284
    def revision_ids_to_search_result(self, result_set):
1976
2285
        """Convert a set of revision ids to a graph SearchResult."""
2306
2615
    # Should fetch trigger a reconcile after the fetch? Only needed for
2307
2616
    # some repository formats that can suffer internal inconsistencies.
2308
2617
    _fetch_reconcile = False
 
2618
    # Does this format have < O(tree_size) delta generation. Used to hint what
 
2619
    # code path for commit, amongst other things.
 
2620
    fast_deltas = None
2309
2621
 
2310
2622
    def __str__(self):
2311
2623
        return "<%s>" % self.__class__.__name__
2590
2902
    InterRepository.get(other).method_name(parameters).
2591
2903
    """
2592
2904
 
2593
 
    _walk_to_common_revisions_batch_size = 1
 
2905
    _walk_to_common_revisions_batch_size = 50
2594
2906
    _optimisers = []
2595
2907
    """The available optimised InterRepository types."""
2596
2908
 
2597
 
    def __init__(self, source, target):
2598
 
        InterObject.__init__(self, source, target)
2599
 
        # These two attributes may be overridden by e.g. InterOtherToRemote to
2600
 
        # provide a faster implementation.
2601
 
        self.target_get_graph = self.target.get_graph
2602
 
        self.target_get_parent_map = self.target.get_parent_map
2603
 
 
2604
2909
    @needs_write_lock
2605
2910
    def copy_content(self, revision_id=None):
2606
2911
        """Make a complete copy of the content in self into destination.
2617
2922
            pass
2618
2923
        self.target.fetch(self.source, revision_id=revision_id)
2619
2924
 
 
2925
    @needs_write_lock
2620
2926
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2621
2927
            fetch_spec=None):
2622
2928
        """Fetch the content required to construct revision_id.
2630
2936
        :return: None.
2631
2937
        """
2632
2938
        from bzrlib.fetch import RepoFetcher
2633
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2634
 
               self.source, self.source._format, self.target,
2635
 
               self.target._format)
2636
2939
        f = RepoFetcher(to_repository=self.target,
2637
2940
                               from_repository=self.source,
2638
2941
                               last_revision=revision_id,
2645
2948
        :param revision_ids: The start point for the search.
2646
2949
        :return: A set of revision ids.
2647
2950
        """
2648
 
        target_graph = self.target_get_graph()
 
2951
        target_graph = self.target.get_graph()
2649
2952
        revision_ids = frozenset(revision_ids)
2650
2953
        # Fast path for the case where all the revisions are already in the
2651
2954
        # target repo.
2698
3001
                break
2699
3002
        return searcher.get_result()
2700
3003
 
2701
 
    @deprecated_method(one_two)
2702
 
    @needs_read_lock
2703
 
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2704
 
        """Return the revision ids that source has that target does not.
2705
 
 
2706
 
        These are returned in topological order.
2707
 
 
2708
 
        :param revision_id: only return revision ids included by this
2709
 
                            revision_id.
2710
 
        :param find_ghosts: If True find missing revisions in deep history
2711
 
            rather than just finding the surface difference.
2712
 
        """
2713
 
        return list(self.search_missing_revision_ids(
2714
 
            revision_id, find_ghosts).get_keys())
2715
 
 
2716
3004
    @needs_read_lock
2717
3005
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2718
3006
        """Return the revision ids that source has that target does not.
2851
3139
        else:
2852
3140
            self.target.fetch(self.source, revision_id=revision_id)
2853
3141
 
2854
 
    @needs_write_lock
2855
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2856
 
            fetch_spec=None):
2857
 
        """See InterRepository.fetch()."""
2858
 
        from bzrlib.fetch import RepoFetcher
2859
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2860
 
               self.source, self.source._format, self.target, self.target._format)
2861
 
        f = RepoFetcher(to_repository=self.target,
2862
 
                               from_repository=self.source,
2863
 
                               last_revision=revision_id,
2864
 
                               fetch_spec=fetch_spec,
2865
 
                               pb=pb, find_ghosts=find_ghosts)
2866
 
 
2867
3142
    @needs_read_lock
2868
3143
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2869
3144
        """See InterRepository.missing_revision_ids()."""
2933
3208
            return False
2934
3209
        return are_knits and InterRepository._same_model(source, target)
2935
3210
 
2936
 
    @needs_write_lock
2937
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
2938
 
            fetch_spec=None):
2939
 
        """See InterRepository.fetch()."""
2940
 
        from bzrlib.fetch import RepoFetcher
2941
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
2942
 
               self.source, self.source._format, self.target, self.target._format)
2943
 
        f = RepoFetcher(to_repository=self.target,
2944
 
                            from_repository=self.source,
2945
 
                            last_revision=revision_id,
2946
 
                            fetch_spec=fetch_spec,
2947
 
                            pb=pb, find_ghosts=find_ghosts)
2948
 
 
2949
3211
    @needs_read_lock
2950
3212
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
2951
3213
        """See InterRepository.missing_revision_ids()."""
3017
3279
            from bzrlib.fetch import RepoFetcher
3018
3280
            fetcher = RepoFetcher(self.target, self.source, revision_id,
3019
3281
                    pb, find_ghosts, fetch_spec=fetch_spec)
3020
 
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
3021
 
               self.source, self.source._format, self.target, self.target._format)
3022
3282
        if fetch_spec is not None:
3023
3283
            if len(list(fetch_spec.heads)) != 1:
3024
3284
                raise AssertionError(
3025
3285
                    "InterPackRepo.fetch doesn't support "
3026
3286
                    "fetching multiple heads yet.")
3027
 
            revision_id = fetch_spec.heads[0]
 
3287
            revision_id = list(fetch_spec.heads)[0]
3028
3288
            fetch_spec = None
3029
3289
        if revision_id is None:
3030
3290
            # TODO:
3034
3294
            # till then:
3035
3295
            source_revision_ids = frozenset(self.source.all_revision_ids())
3036
3296
            revision_ids = source_revision_ids - \
3037
 
                frozenset(self.target_get_parent_map(source_revision_ids))
 
3297
                frozenset(self.target.get_parent_map(source_revision_ids))
3038
3298
            revision_keys = [(revid,) for revid in revision_ids]
3039
 
            target_pack_collection = self._get_target_pack_collection()
3040
 
            index = target_pack_collection.revision_index.combined_index
 
3299
            index = self.target._pack_collection.revision_index.combined_index
3041
3300
            present_revision_ids = set(item[1][0] for item in
3042
3301
                index.iter_entries(revision_keys))
3043
3302
            revision_ids = set(revision_ids) - present_revision_ids
3063
3322
 
3064
3323
    def _pack(self, source, target, revision_ids):
3065
3324
        from bzrlib.repofmt.pack_repo import Packer
3066
 
        target_pack_collection = self._get_target_pack_collection()
3067
3325
        packs = source._pack_collection.all_packs()
3068
 
        pack = Packer(target_pack_collection, packs, '.fetch',
 
3326
        pack = Packer(self.target._pack_collection, packs, '.fetch',
3069
3327
            revision_ids).pack()
3070
3328
        if pack is not None:
3071
 
            target_pack_collection._save_pack_names()
 
3329
            self.target._pack_collection._save_pack_names()
3072
3330
            copied_revs = pack.get_revision_count()
3073
3331
            # Trigger an autopack. This may duplicate effort as we've just done
3074
3332
            # a pack creation, but for now it is simpler to think about as
3075
3333
            # 'upload data, then repack if needed'.
3076
 
            self._autopack()
 
3334
            self.target._pack_collection.autopack()
3077
3335
            return (copied_revs, [])
3078
3336
        else:
3079
3337
            return (0, [])
3080
3338
 
3081
 
    def _autopack(self):
3082
 
        self.target._pack_collection.autopack()
3083
 
 
3084
 
    def _get_target_pack_collection(self):
3085
 
        return self.target._pack_collection
3086
 
 
3087
3339
    @needs_read_lock
3088
3340
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3089
3341
        """See InterRepository.missing_revision_ids().
3096
3348
        elif revision_id is not None:
3097
3349
            # Find ghosts: search for revisions pointing from one repository to
3098
3350
            # the other, and vice versa, anywhere in the history of revision_id.
3099
 
            graph = self.target_get_graph(other_repository=self.source)
 
3351
            graph = self.target.get_graph(other_repository=self.source)
3100
3352
            searcher = graph._make_breadth_first_searcher([revision_id])
3101
3353
            found_ids = set()
3102
3354
            while True:
3112
3364
            # Double query here: should be able to avoid this by changing the
3113
3365
            # graph api further.
3114
3366
            result_set = found_ids - frozenset(
3115
 
                self.target_get_parent_map(found_ids))
 
3367
                self.target.get_parent_map(found_ids))
3116
3368
        else:
3117
3369
            source_ids = self.source.all_revision_ids()
3118
3370
            # source_ids is the worst possible case we may need to pull.
3262
3514
            my_pb = ui.ui_factory.nested_progress_bar()
3263
3515
            pb = my_pb
3264
3516
        else:
 
3517
            symbol_versioning.warn(
 
3518
                symbol_versioning.deprecated_in((1, 14, 0))
 
3519
                % "pb parameter to fetch()")
3265
3520
            my_pb = None
3266
3521
        try:
3267
3522
            self._fetch_all_revisions(revision_ids, pb)
3293
3548
        return basis_id, basis_tree
3294
3549
 
3295
3550
 
3296
 
class InterOtherToRemote(InterRepository):
3297
 
    """An InterRepository that simply delegates to the 'real' InterRepository
3298
 
    calculated for (source, target._real_repository).
3299
 
    """
3300
 
 
3301
 
    _walk_to_common_revisions_batch_size = 50
3302
 
 
3303
 
    def __init__(self, source, target):
3304
 
        InterRepository.__init__(self, source, target)
3305
 
        self._real_inter = None
3306
 
 
3307
 
    @staticmethod
3308
 
    def is_compatible(source, target):
3309
 
        if isinstance(target, remote.RemoteRepository):
3310
 
            return True
3311
 
        return False
3312
 
 
3313
 
    def _ensure_real_inter(self):
3314
 
        if self._real_inter is None:
3315
 
            self.target._ensure_real()
3316
 
            real_target = self.target._real_repository
3317
 
            self._real_inter = InterRepository.get(self.source, real_target)
3318
 
            # Make _real_inter use the RemoteRepository for get_parent_map
3319
 
            self._real_inter.target_get_graph = self.target.get_graph
3320
 
            self._real_inter.target_get_parent_map = self.target.get_parent_map
3321
 
 
3322
 
    def copy_content(self, revision_id=None):
3323
 
        self._ensure_real_inter()
3324
 
        self._real_inter.copy_content(revision_id=revision_id)
3325
 
 
3326
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3327
 
            fetch_spec=None):
3328
 
        self._ensure_real_inter()
3329
 
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
3330
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
3331
 
 
3332
 
    @classmethod
3333
 
    def _get_repo_format_to_test(self):
3334
 
        return None
3335
 
 
3336
 
 
3337
 
class InterRemoteToOther(InterRepository):
3338
 
 
3339
 
    def __init__(self, source, target):
3340
 
        InterRepository.__init__(self, source, target)
3341
 
        self._real_inter = None
3342
 
 
3343
 
    @staticmethod
3344
 
    def is_compatible(source, target):
3345
 
        if not isinstance(source, remote.RemoteRepository):
3346
 
            return False
3347
 
        return InterRepository._same_model(source, target)
3348
 
 
3349
 
    def _ensure_real_inter(self):
3350
 
        if self._real_inter is None:
3351
 
            self.source._ensure_real()
3352
 
            real_source = self.source._real_repository
3353
 
            self._real_inter = InterRepository.get(real_source, self.target)
3354
 
 
3355
 
    @needs_write_lock
3356
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3357
 
            fetch_spec=None):
3358
 
        """See InterRepository.fetch()."""
3359
 
        # Always fetch using the generic streaming fetch code, to allow
3360
 
        # streaming fetching from remote servers.
3361
 
        from bzrlib.fetch import RepoFetcher
3362
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3363
 
                pb, find_ghosts, fetch_spec=fetch_spec)
3364
 
 
3365
 
    def copy_content(self, revision_id=None):
3366
 
        self._ensure_real_inter()
3367
 
        self._real_inter.copy_content(revision_id=revision_id)
3368
 
 
3369
 
    @classmethod
3370
 
    def _get_repo_format_to_test(self):
3371
 
        return None
3372
 
 
3373
 
 
3374
 
 
3375
 
class InterPackToRemotePack(InterPackRepo):
3376
 
    """A specialisation of InterPackRepo for a target that is a
3377
 
    RemoteRepository.
3378
 
 
3379
 
    This will use the get_parent_map RPC rather than plain readvs, and also
3380
 
    uses an RPC for autopacking.
3381
 
    """
3382
 
 
3383
 
    _walk_to_common_revisions_batch_size = 50
3384
 
 
3385
 
    @staticmethod
3386
 
    def is_compatible(source, target):
3387
 
        from bzrlib.repofmt.pack_repo import RepositoryFormatPack
3388
 
        if isinstance(source._format, RepositoryFormatPack):
3389
 
            if isinstance(target, remote.RemoteRepository):
3390
 
                target._format._ensure_real()
3391
 
                if isinstance(target._format._custom_format,
3392
 
                              RepositoryFormatPack):
3393
 
                    if InterRepository._same_model(source, target):
3394
 
                        return True
3395
 
        return False
3396
 
 
3397
 
    def _autopack(self):
3398
 
        self.target.autopack()
3399
 
 
3400
 
    @needs_write_lock
3401
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3402
 
            fetch_spec=None):
3403
 
        """See InterRepository.fetch()."""
3404
 
        if self.target._client._medium._is_remote_before((1, 13)):
3405
 
            # The server won't support the insert_stream RPC, so just use
3406
 
            # regular InterPackRepo logic.  This avoids a bug that causes many
3407
 
            # round-trips for small append calls.
3408
 
            return InterPackRepo.fetch(self, revision_id=revision_id, pb=pb,
3409
 
                find_ghosts=find_ghosts, fetch_spec=fetch_spec)
3410
 
        # Always fetch using the generic streaming fetch code, to allow
3411
 
        # streaming fetching into remote servers.
3412
 
        from bzrlib.fetch import RepoFetcher
3413
 
        fetcher = RepoFetcher(self.target, self.source, revision_id,
3414
 
                              pb, find_ghosts, fetch_spec=fetch_spec)
3415
 
 
3416
 
    def _get_target_pack_collection(self):
3417
 
        return self.target._real_repository._pack_collection
3418
 
 
3419
 
    @classmethod
3420
 
    def _get_repo_format_to_test(self):
3421
 
        return None
3422
 
 
3423
 
 
3424
3551
InterRepository.register_optimiser(InterDifferingSerializer)
3425
3552
InterRepository.register_optimiser(InterSameDataRepository)
3426
3553
InterRepository.register_optimiser(InterWeaveRepo)
3427
3554
InterRepository.register_optimiser(InterKnitRepo)
3428
3555
InterRepository.register_optimiser(InterPackRepo)
3429
 
InterRepository.register_optimiser(InterOtherToRemote)
3430
 
InterRepository.register_optimiser(InterRemoteToOther)
3431
 
InterRepository.register_optimiser(InterPackToRemotePack)
3432
3556
 
3433
3557
 
3434
3558
class CopyConverter(object):
3515
3639
 
3516
3640
class _VersionedFileChecker(object):
3517
3641
 
3518
 
    def __init__(self, repository):
 
3642
    def __init__(self, repository, text_key_references=None):
3519
3643
        self.repository = repository
3520
 
        self.text_index = self.repository._generate_text_key_index()
 
3644
        self.text_index = self.repository._generate_text_key_index(
 
3645
            text_key_references=text_key_references)
3521
3646
 
3522
3647
    def calculate_file_version_parents(self, text_key):
3523
3648
        """Calculate the correct parents for a file version according to