/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: Michael Ellerman
  • Date: 2006-03-09 00:24:48 UTC
  • mto: (1610.1.8 bzr.mbp.integration)
  • mto: This revision was merged to the branch mainline in revision 1616.
  • Revision ID: michael@ellerman.id.au-20060309002448-70cce15e3d605130
Make the "ignore line" in the commit message editor the "right" width, so
that if you make your message that wide it won't wrap in bzr log output.
Just as a visual aid.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# (C) 2005, 2006 Canonical Limited.
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""Reconcilers are able to fix some potential data errors in a branch."""
 
18
 
 
19
 
 
20
__all__ = ['reconcile', 'Reconciler', 'RepoReconciler']
 
21
 
 
22
 
 
23
import bzrlib.branch
 
24
import bzrlib.errors as errors
 
25
import bzrlib.progress
 
26
from bzrlib.trace import mutter
 
27
from bzrlib.tsort import TopoSorter
 
28
import bzrlib.ui as ui
 
29
 
 
30
 
 
31
def reconcile(dir):
 
32
    """Reconcile the data in dir.
 
33
 
 
34
    Currently this is limited to a inventory 'reweave'.
 
35
 
 
36
    This is a convenience method, for using a Reconciler object.
 
37
 
 
38
    Directly using Reconciler is recommended for library users that
 
39
    desire fine grained control or analysis of the found issues.
 
40
    """
 
41
    reconciler = Reconciler(dir)
 
42
    reconciler.reconcile()
 
43
 
 
44
 
 
45
class Reconciler(object):
 
46
    """Reconcilers are used to reconcile existing data."""
 
47
 
 
48
    def __init__(self, dir):
 
49
        self.bzrdir = dir
 
50
 
 
51
    def reconcile(self):
 
52
        """Perform reconciliation.
 
53
        
 
54
        After reconciliation the following attributes document found issues:
 
55
        inconsistent_parents: The number of revisions in the repository whose
 
56
                              ancestry was being reported incorrectly.
 
57
        garbage_inventories: The number of inventory objects without revisions
 
58
                             that were garbage collected.
 
59
        """
 
60
        self.pb = ui.ui_factory.progress_bar()
 
61
        self.repo = self.bzrdir.find_repository()
 
62
        self.pb.note('Reconciling repository %s',
 
63
                     self.repo.bzrdir.root_transport.base)
 
64
        repo_reconciler = RepoReconciler(self.repo)
 
65
        repo_reconciler.reconcile()
 
66
        self.inconsistent_parents = repo_reconciler.inconsistent_parents
 
67
        self.garbage_inventories = repo_reconciler.garbage_inventories
 
68
        self.pb.note('Reconciliation complete.')
 
69
 
 
70
 
 
71
class RepoReconciler(object):
 
72
    """Reconciler that reconciles a repository.
 
73
 
 
74
    Currently this consists of an inventory reweave with revision cross-checks.
 
75
    """
 
76
 
 
77
    def __init__(self, repo):
 
78
        self.repo = repo
 
79
 
 
80
    def reconcile(self):
 
81
        """Perform reconciliation.
 
82
        
 
83
        After reconciliation the following attributes document found issues:
 
84
        inconsistent_parents: The number of revisions in the repository whose
 
85
                              ancestry was being reported incorrectly.
 
86
        garbage_inventories: The number of inventory objects without revisions
 
87
                             that were garbage collected.
 
88
        """
 
89
        self.pb = ui.ui_factory.progress_bar()
 
90
        self.repo.lock_write()
 
91
        try:
 
92
            self._reweave_inventory()
 
93
        finally:
 
94
            self.repo.unlock()
 
95
 
 
96
    def _reweave_inventory(self):
 
97
        """Regenerate the inventory weave for the repository from scratch."""
 
98
        self.pb.update('Reading inventory data.')
 
99
        self.inventory = self.repo.get_inventory_weave()
 
100
        # the total set of revisions to process
 
101
        self.pending = set([file_id for file_id in self.repo.revision_store])
 
102
 
 
103
        # mapping from revision_id to parents
 
104
        self._rev_graph = {}
 
105
        # errors that we detect
 
106
        self.inconsistent_parents = 0
 
107
        # we need the revision id of each revision and its available parents list
 
108
        self._setup_steps(len(self.pending))
 
109
        for rev_id in self.pending:
 
110
            # put a revision into the graph.
 
111
            self._graph_revision(rev_id)
 
112
        # we gc unreferenced inventories too
 
113
        self.garbage_inventories = len(self.inventory.names()) \
 
114
                                   - len(self._rev_graph)
 
115
 
 
116
        if not self.inconsistent_parents and not self.garbage_inventories:
 
117
            self.pb.note('Inventory ok.')
 
118
            return
 
119
        self.pb.update('Backing up inventory...', 0, 0)
 
120
        self.repo.control_weaves.put_weave('inventory.backup',
 
121
                                           self.inventory,
 
122
                                           self.repo.get_transaction())
 
123
        self.pb.note('Backup Inventory created.')
 
124
        # asking for '' should never return a non-empty weave
 
125
        new_inventory = self.repo.control_weaves.get_weave_or_empty('',
 
126
            self.repo.get_transaction())
 
127
 
 
128
        # we have topological order of revisions and non ghost parents ready.
 
129
        self._setup_steps(len(self._rev_graph))
 
130
        for rev_id in TopoSorter(self._rev_graph.items()).iter_topo_order():
 
131
            parents = self._rev_graph[rev_id]
 
132
            # double check this really is in topological order.
 
133
            unavailable = [p for p in parents if p not in new_inventory]
 
134
            assert len(unavailable) == 0
 
135
            # this entry has all the non ghost parents in the inventory
 
136
            # file already.
 
137
            self._reweave_step('adding inventories')
 
138
            new_inventory.add(rev_id, parents, self.inventory.get(rev_id))
 
139
 
 
140
        # if this worked, the set of new_inventory.names should equal
 
141
        # self.pending
 
142
        assert set(new_inventory.names()) == self.pending
 
143
        self.pb.update('Writing weave')
 
144
        self.repo.control_weaves.put_weave('inventory',
 
145
                                           new_inventory,
 
146
                                           self.repo.get_transaction())
 
147
        self.inventory = None
 
148
        self.pb.note('Inventory regenerated.')
 
149
 
 
150
    def _setup_steps(self, new_total):
 
151
        """Setup the markers we need to control the progress bar."""
 
152
        self.total = new_total
 
153
        self.count = 0
 
154
 
 
155
    def _graph_revision(self, rev_id):
 
156
        """Load a revision into the revision graph."""
 
157
        # pick a random revision
 
158
        # analyse revision id rev_id and put it in the stack.
 
159
        self._reweave_step('loading revisions')
 
160
        rev = self.repo.get_revision_reconcile(rev_id)
 
161
        assert rev.revision_id == rev_id
 
162
        parents = []
 
163
        for parent in rev.parent_ids:
 
164
            if self._parent_is_available(parent):
 
165
                parents.append(parent)
 
166
            else:
 
167
                mutter('found ghost %s', parent)
 
168
        self._rev_graph[rev_id] = parents   
 
169
        if set(self.inventory.parent_names(rev_id)) != set(parents):
 
170
            self.inconsistent_parents += 1
 
171
 
 
172
    def _parent_is_available(self, parent):
 
173
        """True if parent is a fully available revision
 
174
 
 
175
        A fully available revision has a inventory and a revision object in the
 
176
        repository.
 
177
        """
 
178
        return (parent in self._rev_graph or 
 
179
                (parent in self.inventory and self.repo.has_revision(parent)))
 
180
 
 
181
    def _reweave_step(self, message):
 
182
        """Mark a single step of regeneration complete."""
 
183
        self.pb.update(message, self.count, self.total)
 
184
        self.count += 1