~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/reconcile.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-06-05 04:05:05 UTC
  • mfrom: (3473.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080605040505-i9kqxg2fps2qjdi0
Add the 'alias' command (Tim Penhey)

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
    )
35
35
from bzrlib.trace import mutter, note
36
36
from bzrlib.tsort import TopoSorter
37
 
from bzrlib.versionedfile import AdapterFactory, FulltextContentFactory
38
37
 
39
38
 
40
39
def reconcile(dir, other=None):
214
213
        from bzrlib.weave import WeaveFile, Weave
215
214
        transaction = self.repo.get_transaction()
216
215
        self.pb.update('Reading inventory data.')
217
 
        self.inventory = self.repo.inventories
218
 
        self.revisions = self.repo.revisions
 
216
        self.inventory = self.repo.get_inventory_weave()
219
217
        # the total set of revisions to process
220
 
        self.pending = set([key[-1] for key in self.revisions.keys()])
 
218
        self.pending = set([rev_id for rev_id in self.repo._revision_store.all_revision_ids(transaction)])
221
219
 
222
220
        # mapping from revision_id to parents
223
221
        self._rev_graph = {}
236
234
            self.pb.note('Inventory ok.')
237
235
            return
238
236
        self.pb.update('Backing up inventory...', 0, 0)
239
 
        self.repo._backup_inventory()
 
237
        self.repo.control_weaves.copy(self.inventory, 'inventory.backup', self.repo.get_transaction())
240
238
        self.pb.note('Backup Inventory created.')
241
 
        new_inventories = self.repo._temp_inventories()
 
239
        # asking for '' should never return a non-empty weave
 
240
        new_inventory_vf = self.repo.control_weaves.get_empty('inventory.new',
 
241
            self.repo.get_transaction())
242
242
 
243
243
        # we have topological order of revisions and non ghost parents ready.
244
244
        self._setup_steps(len(self._rev_graph))
245
 
        revision_keys = [(rev_id,) for rev_id in
246
 
            TopoSorter(self._rev_graph.items()).iter_topo_order()]
247
 
        stream = self._change_inv_parents(
248
 
            self.inventory.get_record_stream(revision_keys, 'unordered', True),
249
 
            self._new_inv_parents,
250
 
            set(revision_keys))
251
 
        new_inventories.insert_record_stream(stream)
252
 
        # if this worked, the set of new_inventories.keys should equal
 
245
        for rev_id in TopoSorter(self._rev_graph.items()).iter_topo_order():
 
246
            parents = self._rev_graph[rev_id]
 
247
            # double check this really is in topological order.
 
248
            unavailable = [p for p in parents if p not in new_inventory_vf]
 
249
            if unavailable:
 
250
                raise AssertionError('unavailable parents: %r'
 
251
                    % unavailable)
 
252
            # this entry has all the non ghost parents in the inventory
 
253
            # file already.
 
254
            self._reweave_step('adding inventories')
 
255
            if isinstance(new_inventory_vf, WeaveFile):
 
256
                # It's really a WeaveFile, but we call straight into the
 
257
                # Weave's add method to disable the auto-write-out behaviour.
 
258
                # This is done to avoid a revision_count * time-to-write additional overhead on 
 
259
                # reconcile.
 
260
                new_inventory_vf._check_write_ok()
 
261
                Weave._add_lines(new_inventory_vf, rev_id, parents,
 
262
                    self.inventory.get_lines(rev_id), None, None, None, False, True)
 
263
            else:
 
264
                new_inventory_vf.add_lines(rev_id, parents, self.inventory.get_lines(rev_id))
 
265
 
 
266
        if isinstance(new_inventory_vf, WeaveFile):
 
267
            new_inventory_vf._save()
 
268
        # if this worked, the set of new_inventory_vf.names should equal
253
269
        # self.pending
254
 
        if not (set(new_inventories.keys()) ==
255
 
            set([(revid,) for revid in self.pending])):
 
270
        if not (set(new_inventory_vf.versions()) == self.pending):
256
271
            raise AssertionError()
257
272
        self.pb.update('Writing weave')
258
 
        self.repo._activate_new_inventory()
 
273
        self.repo.control_weaves.copy(new_inventory_vf, 'inventory', self.repo.get_transaction())
 
274
        self.repo.control_weaves.delete('inventory.new', self.repo.get_transaction())
259
275
        self.inventory = None
260
276
        self.pb.note('Inventory regenerated.')
261
277
 
262
 
    def _new_inv_parents(self, revision_key):
263
 
        """Lookup ghost-filtered parents for revision_key."""
264
 
        # Use the filtered ghostless parents list:
265
 
        return tuple([(revid,) for revid in self._rev_graph[revision_key[-1]]])
266
 
 
267
 
    def _change_inv_parents(self, stream, get_parents, all_revision_keys):
268
 
        """Adapt a record stream to reconcile the parents."""
269
 
        for record in stream:
270
 
            wanted_parents = get_parents(record.key)
271
 
            if wanted_parents and wanted_parents[0] not in all_revision_keys:
272
 
                # The check for the left most parent only handles knit
273
 
                # compressors, but this code only applies to knit and weave
274
 
                # repositories anyway.
275
 
                bytes = record.get_bytes_as('fulltext')
276
 
                yield FulltextContentFactory(record.key, wanted_parents, record.sha1, bytes)
277
 
            else:
278
 
                adapted_record = AdapterFactory(record.key, wanted_parents, record)
279
 
                yield adapted_record
280
 
            self._reweave_step('adding inventories')
281
 
 
282
278
    def _setup_steps(self, new_total):
283
279
        """Setup the markers we need to control the progress bar."""
284
280
        self.total = new_total
297
293
            else:
298
294
                mutter('found ghost %s', parent)
299
295
        self._rev_graph[rev_id] = parents
 
296
        if self._parents_are_inconsistent(rev_id, parents):
 
297
            self.inconsistent_parents += 1
 
298
            mutter('Inconsistent inventory parents: id {%s} '
 
299
                   'inventory claims %r, '
 
300
                   'available parents are %r, '
 
301
                   'unavailable parents are %r',
 
302
                   rev_id,
 
303
                   set(self.inventory.get_parent_map([rev_id])[rev_id]),
 
304
                   set(parents),
 
305
                   set(rev.parent_ids).difference(set(parents)))
 
306
 
 
307
    def _parents_are_inconsistent(self, rev_id, parents):
 
308
        """Return True if the parents list of rev_id does not match the weave.
 
309
 
 
310
        This detects inconsistencies based on the self.thorough value:
 
311
        if thorough is on, the first parent value is checked as well as ghost
 
312
        differences.
 
313
        Otherwise only the ghost differences are evaluated.
 
314
        """
 
315
        weave_parents = self.inventory.get_parent_map([rev_id])[rev_id]
 
316
        weave_missing_old_ghosts = set(weave_parents) != set(parents)
 
317
        first_parent_is_wrong = (
 
318
            len(weave_parents) and len(parents) and
 
319
            parents[0] != weave_parents[0])
 
320
        if self.thorough:
 
321
            return weave_missing_old_ghosts or first_parent_is_wrong
 
322
        else:
 
323
            return weave_missing_old_ghosts
300
324
 
301
325
    def _check_garbage_inventories(self):
302
326
        """Check for garbage inventories which we cannot trust
306
330
        """
307
331
        if not self.thorough:
308
332
            return
309
 
        inventories = set(self.inventory.keys())
310
 
        revisions = set(self.revisions.keys())
 
333
        inventories = set(self.inventory.versions())
 
334
        revisions = set(self._rev_graph.keys())
311
335
        garbage = inventories.difference(revisions)
312
336
        self.garbage_inventories = len(garbage)
313
 
        for revision_key in garbage:
314
 
            mutter('Garbage inventory {%s} found.', revision_key[-1])
 
337
        for revision_id in garbage:
 
338
            mutter('Garbage inventory {%s} found.', revision_id)
315
339
 
316
340
    def _parent_is_available(self, parent):
317
341
        """True if parent is a fully available revision
319
343
        A fully available revision has a inventory and a revision object in the
320
344
        repository.
321
345
        """
322
 
        if parent in self._rev_graph:
323
 
            return True
324
 
        inv_present = (1 == len(self.inventory.get_parent_map([(parent,)])))
325
 
        return (inv_present and self.repo.has_revision(parent))
 
346
        return (parent in self._rev_graph or 
 
347
                (parent in self.inventory and self.repo.has_revision(parent)))
326
348
 
327
349
    def _reweave_step(self, message):
328
350
        """Mark a single step of regeneration complete."""
352
374
        """Load indexes for the reconciliation."""
353
375
        self.transaction = self.repo.get_transaction()
354
376
        self.pb.update('Reading indexes.', 0, 2)
355
 
        self.inventory = self.repo.inventories
 
377
        self.inventory = self.repo.get_inventory_weave()
356
378
        self.pb.update('Reading indexes.', 1, 2)
357
379
        self.repo._check_for_inconsistent_revision_parents()
358
 
        self.revisions = self.repo.revisions
 
380
        self.revisions = self.repo._revision_store.get_revision_file(self.transaction)
359
381
        self.pb.update('Reading indexes.', 2, 2)
360
382
 
361
383
    def _gc_inventory(self):
367
389
            self.pb.note('Inventory ok.')
368
390
            return
369
391
        self.pb.update('Backing up inventory...', 0, 0)
370
 
        self.repo._backup_inventory()
 
392
        self.repo.control_weaves.copy(self.inventory, 'inventory.backup', self.transaction)
371
393
        self.pb.note('Backup Inventory created.')
372
394
        # asking for '' should never return a non-empty weave
373
 
        new_inventories = self.repo._temp_inventories()
 
395
        new_inventory_vf = self.repo.control_weaves.get_empty('inventory.new',
 
396
            self.transaction)
 
397
 
374
398
        # we have topological order of revisions and non ghost parents ready.
375
 
        graph = self.revisions.get_parent_map(self.revisions.keys())
376
 
        revision_keys = list(TopoSorter(graph).iter_topo_order())
377
 
        revision_ids = [key[-1] for key in revision_keys]
378
 
        self._setup_steps(len(revision_keys))
379
 
        stream = self._change_inv_parents(
380
 
            self.inventory.get_record_stream(revision_keys, 'unordered', True),
381
 
            graph.__getitem__,
382
 
            set(revision_keys))
383
 
        new_inventories.insert_record_stream(stream)
 
399
        self._setup_steps(len(self.revisions))
 
400
        revision_ids = self.revisions.versions()
 
401
        graph = self.revisions.get_parent_map(revision_ids)
 
402
        for rev_id in TopoSorter(graph.items()).iter_topo_order():
 
403
            parents = graph[rev_id]
 
404
            # double check this really is in topological order, ignoring existing ghosts.
 
405
            unavailable = [p for p in parents if p not in new_inventory_vf and
 
406
                p in self.revisions]
 
407
            if unavailable:
 
408
                raise AssertionError(
 
409
                    'unavailable parents: %r' % (unavailable,))
 
410
            # this entry has all the non ghost parents in the inventory
 
411
            # file already.
 
412
            self._reweave_step('adding inventories')
 
413
            # ugly but needed, weaves are just way tooooo slow else.
 
414
            new_inventory_vf.add_lines_with_ghosts(rev_id, parents,
 
415
                self.inventory.get_lines(rev_id))
 
416
 
384
417
        # if this worked, the set of new_inventory_vf.names should equal
385
 
        # the revisionds list
386
 
        if not(set(new_inventories.keys()) == set(revision_keys)):
 
418
        # self.pending
 
419
        if not(set(new_inventory_vf.versions()) == set(self.revisions.versions())):
387
420
            raise AssertionError()
388
421
        self.pb.update('Writing weave')
389
 
        self.repo._activate_new_inventory()
 
422
        self.repo.control_weaves.copy(new_inventory_vf, 'inventory', self.transaction)
 
423
        self.repo.control_weaves.delete('inventory.new', self.transaction)
390
424
        self.inventory = None
391
425
        self.pb.note('Inventory regenerated.')
392
426
 
 
427
    def _check_garbage_inventories(self):
 
428
        """Check for garbage inventories which we cannot trust
 
429
 
 
430
        We cant trust them because their pre-requisite file data may not
 
431
        be present - all we know is that their revision was not installed.
 
432
        """
 
433
        inventories = set(self.inventory.versions())
 
434
        revisions = set(self.revisions.versions())
 
435
        garbage = inventories.difference(revisions)
 
436
        self.garbage_inventories = len(garbage)
 
437
        for revision_id in garbage:
 
438
            mutter('Garbage inventory {%s} found.', revision_id)
 
439
 
393
440
    def _fix_text_parents(self):
394
441
        """Fix bad versionedfile parent entries.
395
442
 
400
447
        parent lists, and replaces the versionedfile with a corrected version.
401
448
        """
402
449
        transaction = self.repo.get_transaction()
403
 
        versions = [key[-1] for key in self.revisions.keys()]
 
450
        versions = self.revisions.versions()
404
451
        mutter('Prepopulating revision text cache with %d revisions',
405
452
                len(versions))
406
453
        vf_checker = self.repo._get_versioned_file_checker()
407
 
        bad_parents, unused_versions = vf_checker.check_file_version_parents(
408
 
            self.repo.texts, self.pb)
409
 
        text_index = vf_checker.text_index
410
 
        per_id_bad_parents = {}
411
 
        for key in unused_versions:
412
 
            # Ensure that every file with unused versions gets rewritten.
413
 
            # NB: This is really not needed, reconcile != pack.
414
 
            per_id_bad_parents[key[0]] = {}
415
 
        # Generate per-knit/weave data.
416
 
        for key, details in bad_parents.iteritems():
417
 
            file_id = key[0]
418
 
            rev_id = key[1]
419
 
            knit_parents = tuple([parent[-1] for parent in details[0]])
420
 
            correct_parents = tuple([parent[-1] for parent in details[1]])
421
 
            file_details = per_id_bad_parents.setdefault(file_id, {})
422
 
            file_details[rev_id] = (knit_parents, correct_parents)
423
 
        file_id_versions = {}
424
 
        for text_key in text_index:
425
 
            versions_list = file_id_versions.setdefault(text_key[0], [])
426
 
            versions_list.append(text_key[1])
427
 
        # Do the reconcile of individual weaves.
428
 
        for num, file_id in enumerate(per_id_bad_parents):
 
454
        # List all weaves before altering, to avoid race conditions when we
 
455
        # delete unused weaves.
 
456
        weaves = list(enumerate(self.repo.weave_store))
 
457
        for num, file_id in weaves:
429
458
            self.pb.update('Fixing text parents', num,
430
 
                           len(per_id_bad_parents))
431
 
            versions_with_bad_parents = per_id_bad_parents[file_id]
432
 
            id_unused_versions = set(key[-1] for key in unused_versions
433
 
                if key[0] == file_id)
434
 
            if file_id in file_id_versions:
435
 
                file_versions = file_id_versions[file_id]
436
 
            else:
437
 
                # This id was present in the disk store but is not referenced
438
 
                # by any revision at all.
439
 
                file_versions = []
440
 
            self._fix_text_parent(file_id, versions_with_bad_parents,
441
 
                 id_unused_versions, file_versions)
 
459
                           len(self.repo.weave_store))
 
460
            vf = self.repo.weave_store.get_weave(file_id, transaction)
 
461
            versions_with_bad_parents, unused_versions = \
 
462
                vf_checker.check_file_version_parents(vf, file_id)
 
463
            if (len(versions_with_bad_parents) == 0 and
 
464
                len(unused_versions) == 0):
 
465
                continue
 
466
            full_text_versions = set()
 
467
            self._fix_text_parent(file_id, vf, versions_with_bad_parents,
 
468
                full_text_versions, unused_versions)
442
469
 
443
 
    def _fix_text_parent(self, file_id, versions_with_bad_parents,
444
 
            unused_versions, all_versions):
 
470
    def _fix_text_parent(self, file_id, vf, versions_with_bad_parents,
 
471
            full_text_versions, unused_versions):
445
472
        """Fix bad versionedfile entries in a single versioned file."""
446
473
        mutter('fixing text parent: %r (%d versions)', file_id,
447
474
                len(versions_with_bad_parents))
448
 
        mutter('(%d are unused)', len(unused_versions))
449
 
        new_file_id = 'temp:%s' % file_id
 
475
        mutter('(%d need to be full texts, %d are unused)',
 
476
                len(full_text_versions), len(unused_versions))
 
477
        new_vf = self.repo.weave_store.get_empty('temp:%s' % file_id,
 
478
            self.transaction)
450
479
        new_parents = {}
451
 
        needed_keys = set()
452
 
        for version in all_versions:
 
480
        for version in vf.versions():
453
481
            if version in unused_versions:
454
482
                continue
455
483
            elif version in versions_with_bad_parents:
456
484
                parents = versions_with_bad_parents[version][1]
457
485
            else:
458
 
                pmap = self.repo.texts.get_parent_map([(file_id, version)])
459
 
                parents = [key[-1] for key in pmap[(file_id, version)]]
460
 
            new_parents[(new_file_id, version)] = [
461
 
                (new_file_id, parent) for parent in parents]
462
 
            needed_keys.add((file_id, version))
463
 
        def fix_parents(stream):
464
 
            for record in stream:
465
 
                bytes = record.get_bytes_as('fulltext')
466
 
                new_key = (new_file_id, record.key[-1])
467
 
                parents = new_parents[new_key]
468
 
                yield FulltextContentFactory(new_key, parents, record.sha1, bytes)
469
 
        stream = self.repo.texts.get_record_stream(needed_keys, 'topological', True)
470
 
        self.repo._remove_file_id(new_file_id)
471
 
        self.repo.texts.insert_record_stream(fix_parents(stream))
472
 
        self.repo._remove_file_id(file_id)
473
 
        if len(new_parents):
474
 
            self.repo._move_file_id(new_file_id, file_id)
 
486
                parents = vf.get_parent_map([version])[version]
 
487
            new_parents[version] = parents
 
488
        if not len(new_parents):
 
489
            # No used versions, remove the VF.
 
490
            self.repo.weave_store.delete(file_id, self.transaction)
 
491
            return
 
492
        for version in TopoSorter(new_parents.items()).iter_topo_order():
 
493
            lines = vf.get_lines(version)
 
494
            parents = new_parents[version]
 
495
            if parents and (parents[0] in full_text_versions):
 
496
                # Force this record to be a fulltext, not a delta.
 
497
                new_vf._add(version, lines, parents, False,
 
498
                    None, None, None, False)
 
499
            else:
 
500
                new_vf.add_lines(version, parents, lines)
 
501
        self.repo.weave_store.copy(new_vf, file_id, self.transaction)
 
502
        self.repo.weave_store.delete('temp:%s' % file_id, self.transaction)
475
503
 
476
504
 
477
505
class PackReconciler(RepoReconciler):