/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/reconcile.py

  • Committer: Andrew Bennetts
  • Date: 2008-08-12 14:53:26 UTC
  • mto: This revision was merged to the branch mainline in revision 3624.
  • Revision ID: andrew.bennetts@canonical.com-20080812145326-yx693x2jc4rcovb7
Move the notes on writing tests out of HACKING into a new file, and improve
them.

Many of the testing notes in the HACKING file were in duplicated in two places
in that file!  This change removes that duplication.  It also adds new sections
on “Where should I put a new test?” and “TestCase and its subclasses”, and
others like “Test feature dependencies” have been expanded.  The whole document
has generally been edited to be a bit more coherent. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Reconcilers are able to fix some potential data errors in a branch."""
18
18
 
27
27
 
28
28
 
29
29
from bzrlib import (
30
 
    cleanup,
31
30
    errors,
32
31
    ui,
 
32
    repository,
 
33
    repofmt,
33
34
    )
34
 
from bzrlib.trace import mutter
35
 
from bzrlib.tsort import topo_sort
 
35
from bzrlib.trace import mutter, note
 
36
from bzrlib.tsort import TopoSorter
36
37
from bzrlib.versionedfile import AdapterFactory, FulltextContentFactory
37
38
 
38
39
 
61
62
 
62
63
    def reconcile(self):
63
64
        """Perform reconciliation.
64
 
 
 
65
        
65
66
        After reconciliation the following attributes document found issues:
66
67
        inconsistent_parents: The number of revisions in the repository whose
67
68
                              ancestry was being reported incorrectly.
89
90
            # Nothing to check here
90
91
            self.fixed_branch_history = None
91
92
            return
92
 
        ui.ui_factory.note('Reconciling branch %s' % self.branch.base)
 
93
        self.pb.note('Reconciling branch %s',
 
94
                     self.branch.base)
93
95
        branch_reconciler = self.branch.reconcile(thorough=True)
94
96
        self.fixed_branch_history = branch_reconciler.fixed_history
95
97
 
96
98
    def _reconcile_repository(self):
97
99
        self.repo = self.bzrdir.find_repository()
98
 
        ui.ui_factory.note('Reconciling repository %s' %
99
 
            self.repo.user_url)
 
100
        self.pb.note('Reconciling repository %s',
 
101
                     self.repo.bzrdir.root_transport.base)
100
102
        self.pb.update("Reconciling repository", 0, 1)
101
103
        repo_reconciler = self.repo.reconcile(thorough=True)
102
104
        self.inconsistent_parents = repo_reconciler.inconsistent_parents
103
105
        self.garbage_inventories = repo_reconciler.garbage_inventories
104
106
        if repo_reconciler.aborted:
105
 
            ui.ui_factory.note(
 
107
            self.pb.note(
106
108
                'Reconcile aborted: revision index has inconsistent parents.')
107
 
            ui.ui_factory.note(
 
109
            self.pb.note(
108
110
                'Run "bzr check" for more details.')
109
111
        else:
110
 
            ui.ui_factory.note('Reconciliation complete.')
 
112
            self.pb.note('Reconciliation complete.')
111
113
 
112
114
 
113
115
class BranchReconciler(object):
119
121
        self.branch = a_branch
120
122
 
121
123
    def reconcile(self):
122
 
        operation = cleanup.OperationWithCleanups(self._reconcile)
123
 
        self.add_cleanup = operation.add_cleanup
124
 
        operation.run_simple()
125
 
 
126
 
    def _reconcile(self):
127
124
        self.branch.lock_write()
128
 
        self.add_cleanup(self.branch.unlock)
129
 
        self.pb = ui.ui_factory.nested_progress_bar()
130
 
        self.add_cleanup(self.pb.finished)
131
 
        self._reconcile_steps()
 
125
        try:
 
126
            self.pb = ui.ui_factory.nested_progress_bar()
 
127
            try:
 
128
                self._reconcile_steps()
 
129
            finally:
 
130
                self.pb.finished()
 
131
        finally:
 
132
            self.branch.unlock()
132
133
 
133
134
    def _reconcile_steps(self):
134
135
        self._reconcile_revision_history()
136
137
    def _reconcile_revision_history(self):
137
138
        repo = self.branch.repository
138
139
        last_revno, last_revision_id = self.branch.last_revision_info()
139
 
        real_history = []
140
 
        try:
141
 
            for revid in repo.iter_reverse_revision_history(
142
 
                    last_revision_id):
143
 
                real_history.append(revid)
144
 
        except errors.RevisionNotPresent:
145
 
            pass # Hit a ghost left hand parent
 
140
        real_history = list(repo.iter_reverse_revision_history(
 
141
                                last_revision_id))
146
142
        real_history.reverse()
147
143
        if last_revno != len(real_history):
148
144
            self.fixed_history = True
150
146
            # set_revision_history, as this will regenerate it again.
151
147
            # Not really worth a whole BranchReconciler class just for this,
152
148
            # though.
153
 
            ui.ui_factory.note('Fixing last revision info %s => %s' % (
154
 
                 last_revno, len(real_history)))
 
149
            self.pb.note('Fixing last revision info %s => %s',
 
150
                         last_revno, len(real_history))
155
151
            self.branch.set_last_revision_info(len(real_history),
156
152
                                               last_revision_id)
157
153
        else:
158
154
            self.fixed_history = False
159
 
            ui.ui_factory.note('revision_history ok.')
 
155
            self.pb.note('revision_history ok.')
160
156
 
161
157
 
162
158
class RepoReconciler(object):
163
159
    """Reconciler that reconciles a repository.
164
160
 
165
161
    The goal of repository reconciliation is to make any derived data
166
 
    consistent with the core data committed by a user. This can involve
 
162
    consistent with the core data committed by a user. This can involve 
167
163
    reindexing, or removing unreferenced data if that can interfere with
168
164
    queries in a given repository.
169
165
 
185
181
 
186
182
    def reconcile(self):
187
183
        """Perform reconciliation.
188
 
 
 
184
        
189
185
        After reconciliation the following attributes document found issues:
190
186
        inconsistent_parents: The number of revisions in the repository whose
191
187
                              ancestry was being reported incorrectly.
192
188
        garbage_inventories: The number of inventory objects without revisions
193
189
                             that were garbage collected.
194
190
        """
195
 
        operation = cleanup.OperationWithCleanups(self._reconcile)
196
 
        self.add_cleanup = operation.add_cleanup
197
 
        operation.run_simple()
198
 
 
199
 
    def _reconcile(self):
200
191
        self.repo.lock_write()
201
 
        self.add_cleanup(self.repo.unlock)
202
 
        self.pb = ui.ui_factory.nested_progress_bar()
203
 
        self.add_cleanup(self.pb.finished)
204
 
        self._reconcile_steps()
 
192
        try:
 
193
            self.pb = ui.ui_factory.nested_progress_bar()
 
194
            try:
 
195
                self._reconcile_steps()
 
196
            finally:
 
197
                self.pb.finished()
 
198
        finally:
 
199
            self.repo.unlock()
205
200
 
206
201
    def _reconcile_steps(self):
207
202
        """Perform the steps to reconcile this repository."""
209
204
 
210
205
    def _reweave_inventory(self):
211
206
        """Regenerate the inventory weave for the repository from scratch.
212
 
 
213
 
        This is a smart function: it will only do the reweave if doing it
 
207
        
 
208
        This is a smart function: it will only do the reweave if doing it 
214
209
        will correct data issues. The self.thorough flag controls whether
215
210
        only data-loss causing issues (!self.thorough) or all issues
216
211
        (self.thorough) are treated as requiring the reweave.
218
213
        # local because needing to know about WeaveFile is a wart we want to hide
219
214
        from bzrlib.weave import WeaveFile, Weave
220
215
        transaction = self.repo.get_transaction()
221
 
        self.pb.update('Reading inventory data')
 
216
        self.pb.update('Reading inventory data.')
222
217
        self.inventory = self.repo.inventories
223
218
        self.revisions = self.repo.revisions
224
219
        # the total set of revisions to process
234
229
            # put a revision into the graph.
235
230
            self._graph_revision(rev_id)
236
231
        self._check_garbage_inventories()
237
 
        # if there are no inconsistent_parents and
 
232
        # if there are no inconsistent_parents and 
238
233
        # (no garbage inventories or we are not doing a thorough check)
239
 
        if (not self.inconsistent_parents and
 
234
        if (not self.inconsistent_parents and 
240
235
            (not self.garbage_inventories or not self.thorough)):
241
 
            ui.ui_factory.note('Inventory ok.')
 
236
            self.pb.note('Inventory ok.')
242
237
            return
243
 
        self.pb.update('Backing up inventory', 0, 0)
 
238
        self.pb.update('Backing up inventory...', 0, 0)
244
239
        self.repo._backup_inventory()
245
 
        ui.ui_factory.note('Backup inventory created.')
 
240
        self.pb.note('Backup Inventory created.')
246
241
        new_inventories = self.repo._temp_inventories()
247
242
 
248
243
        # we have topological order of revisions and non ghost parents ready.
249
244
        self._setup_steps(len(self._rev_graph))
250
 
        revision_keys = [(rev_id,) for rev_id in topo_sort(self._rev_graph)]
 
245
        revision_keys = [(rev_id,) for rev_id in
 
246
            TopoSorter(self._rev_graph.items()).iter_topo_order()]
251
247
        stream = self._change_inv_parents(
252
 
            self.inventory.get_record_stream(revision_keys, 'unordered', True),
 
248
            self.inventory.get_record_stream(revision_keys, 'unsorted', True),
253
249
            self._new_inv_parents,
254
250
            set(revision_keys))
255
251
        new_inventories.insert_record_stream(stream)
261
257
        self.pb.update('Writing weave')
262
258
        self.repo._activate_new_inventory()
263
259
        self.inventory = None
264
 
        ui.ui_factory.note('Inventory regenerated.')
 
260
        self.pb.note('Inventory regenerated.')
265
261
 
266
262
    def _new_inv_parents(self, revision_key):
267
263
        """Lookup ghost-filtered parents for revision_key."""
355
351
    def _load_indexes(self):
356
352
        """Load indexes for the reconciliation."""
357
353
        self.transaction = self.repo.get_transaction()
358
 
        self.pb.update('Reading indexes', 0, 2)
 
354
        self.pb.update('Reading indexes.', 0, 2)
359
355
        self.inventory = self.repo.inventories
360
 
        self.pb.update('Reading indexes', 1, 2)
 
356
        self.pb.update('Reading indexes.', 1, 2)
361
357
        self.repo._check_for_inconsistent_revision_parents()
362
358
        self.revisions = self.repo.revisions
363
 
        self.pb.update('Reading indexes', 2, 2)
 
359
        self.pb.update('Reading indexes.', 2, 2)
364
360
 
365
361
    def _gc_inventory(self):
366
362
        """Remove inventories that are not referenced from the revision store."""
367
 
        self.pb.update('Checking unused inventories', 0, 1)
 
363
        self.pb.update('Checking unused inventories.', 0, 1)
368
364
        self._check_garbage_inventories()
369
 
        self.pb.update('Checking unused inventories', 1, 3)
 
365
        self.pb.update('Checking unused inventories.', 1, 3)
370
366
        if not self.garbage_inventories:
371
 
            ui.ui_factory.note('Inventory ok.')
 
367
            self.pb.note('Inventory ok.')
372
368
            return
373
 
        self.pb.update('Backing up inventory', 0, 0)
 
369
        self.pb.update('Backing up inventory...', 0, 0)
374
370
        self.repo._backup_inventory()
375
 
        ui.ui_factory.note('Backup Inventory created')
 
371
        self.pb.note('Backup Inventory created.')
376
372
        # asking for '' should never return a non-empty weave
377
373
        new_inventories = self.repo._temp_inventories()
378
374
        # we have topological order of revisions and non ghost parents ready.
379
375
        graph = self.revisions.get_parent_map(self.revisions.keys())
380
 
        revision_keys = topo_sort(graph)
 
376
        revision_keys = list(TopoSorter(graph).iter_topo_order())
381
377
        revision_ids = [key[-1] for key in revision_keys]
382
378
        self._setup_steps(len(revision_keys))
383
379
        stream = self._change_inv_parents(
384
 
            self.inventory.get_record_stream(revision_keys, 'unordered', True),
 
380
            self.inventory.get_record_stream(revision_keys, 'unsorted', True),
385
381
            graph.__getitem__,
386
382
            set(revision_keys))
387
383
        new_inventories.insert_record_stream(stream)
392
388
        self.pb.update('Writing weave')
393
389
        self.repo._activate_new_inventory()
394
390
        self.inventory = None
395
 
        ui.ui_factory.note('Inventory regenerated.')
 
391
        self.pb.note('Inventory regenerated.')
396
392
 
397
393
    def _fix_text_parents(self):
398
394
        """Fix bad versionedfile parent entries.
503
499
        collection = self.repo._pack_collection
504
500
        collection.ensure_loaded()
505
501
        collection.lock_names()
506
 
        self.add_cleanup(collection._unlock_names)
507
 
        packs = collection.all_packs()
508
 
        all_revisions = self.repo.all_revision_ids()
509
 
        total_inventories = len(list(
510
 
            collection.inventory_index.combined_index.iter_all_entries()))
511
 
        if len(all_revisions):
512
 
            new_pack =  self.repo._reconcile_pack(collection, packs,
513
 
                ".reconcile", all_revisions, self.pb)
514
 
            if new_pack is not None:
 
502
        try:
 
503
            packs = collection.all_packs()
 
504
            all_revisions = self.repo.all_revision_ids()
 
505
            total_inventories = len(list(
 
506
                collection.inventory_index.combined_index.iter_all_entries()))
 
507
            if len(all_revisions):
 
508
                self._packer = repofmt.pack_repo.ReconcilePacker(
 
509
                    collection, packs, ".reconcile", all_revisions)
 
510
                new_pack = self._packer.pack(pb=self.pb)
 
511
                if new_pack is not None:
 
512
                    self._discard_and_save(packs)
 
513
            else:
 
514
                # only make a new pack when there is data to copy.
515
515
                self._discard_and_save(packs)
516
 
        else:
517
 
            # only make a new pack when there is data to copy.
518
 
            self._discard_and_save(packs)
519
 
        self.garbage_inventories = total_inventories - len(list(
520
 
            collection.inventory_index.combined_index.iter_all_entries()))
 
516
            self.garbage_inventories = total_inventories - len(list(
 
517
                collection.inventory_index.combined_index.iter_all_entries()))
 
518
        finally:
 
519
            collection._unlock_names()
521
520
 
522
521
    def _discard_and_save(self, packs):
523
522
        """Discard some packs from the repository.