49
47
reconciler.reconcile()
52
class Reconciler(object):
53
"""Reconcilers are used to reconcile existing data.
55
Currently this is limited to a single repository, and consists
56
of an inventory reweave with revision cross-checks.
50
def topological_sort(graph):
51
"""Topological sort a graph.
53
graph -- sequence of pairs of node->parents_list.
55
The result is a list of node names, such that all parents come before
58
Nodes at the same depth are returned in sorted order.
60
node identifiers can be any hashable object, and are typically strings.
59
def __init__(self, dir):
63
"""Actually perform the reconciliation."""
64
self.pb = ui.ui_factory.progress_bar()
65
self.repo = self.bzrdir.open_repository()
66
self.repo.lock_write()
68
self.pb.note('Reconciling repository %s',
69
self.repo.bzrdir.root_transport.base)
70
self._reweave_inventory()
73
self.pb.note('Reconciliation complete.')
75
def _reweave_inventory(self):
76
"""Regenerate the inventory weave for the repository from scratch."""
77
self.pb.update('Reading inventory data.')
78
self.inventory = self.repo.get_inventory_weave()
79
self.repo.control_weaves.put_weave('inventory.backup',
81
self.repo.get_transaction())
82
self.pb.note('Backup Inventory created.')
83
# asking for '' should never return a non-empty weave
84
new_inventory = self.repo.control_weaves.get_weave_or_empty('',
85
self.repo.get_transaction())
64
return sorter._work_queue
67
class TopoSorter(object):
69
def sort(self, graph):
87
70
# the total set of revisions to process
88
self.pending = set([file_id for file_id in self.repo.revision_store])
71
self._rev_graph = dict(graph)
73
# self._original_graph = dict(graph)
91
75
# the current line of history being processed.
92
76
# once we add in per inventory root ids this will involve a single
93
77
# pass within the revision knit at that point.
94
# these contain respectively the revision id and the not_ghost_parents.
78
# these contain respectively the revision id in the call stack
95
79
self._rev_stack = []
96
self._parent_stack = []
80
# a set of the revisions on the stack, for cycle detection
82
# and the not_ghost_parents.
97
83
self._pending_stack = []
98
84
# actual queue to reinsert. This is a single pass queue we can use to
99
85
# regenerate the inventory versioned file, and holds
102
88
# this is a set for fast lookups of queued revisions
103
89
self._work_set = set()
104
90
# total steps = 1 read per revision + one insert into the inventory
105
self.total = len(self.pending) * 2
108
# mapping from revision_id to parents
110
# we need the revision id of each revision and its available parents list
111
# we could do this on demand, but its easy to just build a graph.
112
for rev_id in self.pending:
113
# put a revision into the to-process queue
114
self._graph_revision(rev_id)
116
92
# now we do a depth first search of the revision graph until its empty.
117
93
# this gives us a topological order across all revisions in
173
147
# we are now recursing down a call stack.
174
148
# its a width first search which
176
### Useful if fiddling with this code.
150
### Useful if fiddling with this code.
177
151
### # cross check
178
152
### for index in range(len(self._work_queue)):
179
### rev = self._work_queue[index][0]
153
### rev = self._work_queue[index]
180
154
### for left_index in range(index):
181
### if rev in self._work_queue[left_index][1]:
155
### if rev in self.original_graph[self._work_queue[left_index]]:
182
156
### print "revision in parent list of earlier revision"
183
157
### import pdb;pdb.set_trace()
159
def _stack_revision(self, rev_id, parents):
160
"""Add rev_id to the pending revision stack."""
162
if rev_id in self._rev_set:
163
# we only supply the revisions that led to the cycle. This isn't
164
# minimal though... but it is usually a subset of the entire graph
165
# and easier to debug.
166
raise errors.GraphCycleError(self._rev_set)
168
self._rev_stack.append(rev_id)
169
self._rev_set.add(rev_id)
170
self._pending_stack.append(list(parents))
172
def _unstack_revision(self):
173
"""A revision has been completed.
175
The revision is added to the work queue, and the data for
176
it popped from the call stack.
178
rev_id = self._rev_stack.pop()
179
self._rev_set.remove(rev_id)
180
self._pending_stack.pop()
181
self._work_queue.append(rev_id)
182
self._work_set.add(rev_id)
183
# and remove it from the rev graph as its now complete
184
self._rev_graph.pop(rev_id)
187
class Reconciler(object):
188
"""Reconcilers are used to reconcile existing data.
190
Currently this is limited to a single repository, and consists
191
of an inventory reweave with revision cross-checks.
194
def __init__(self, dir):
198
"""Actually perform the reconciliation."""
199
self.pb = ui.ui_factory.progress_bar()
200
self.repo = self.bzrdir.open_repository()
201
self.repo.lock_write()
203
self.pb.note('Reconciling repository %s',
204
self.repo.bzrdir.root_transport.base)
205
self._reweave_inventory()
208
self.pb.note('Reconciliation complete.')
210
def _reweave_inventory(self):
211
"""Regenerate the inventory weave for the repository from scratch."""
212
self.pb.update('Reading inventory data.')
213
self.inventory = self.repo.get_inventory_weave()
214
self.repo.control_weaves.put_weave('inventory.backup',
216
self.repo.get_transaction())
217
self.pb.note('Backup Inventory created.')
218
# asking for '' should never return a non-empty weave
219
new_inventory = self.repo.control_weaves.get_weave_or_empty('',
220
self.repo.get_transaction())
222
# the total set of revisions to process
223
self.pending = set([file_id for file_id in self.repo.revision_store])
225
# total steps = 1 read per revision + one insert into the inventory
226
self.total = len(self.pending) * 2
229
# mapping from revision_id to parents
231
# we need the revision id of each revision and its available parents list
232
for rev_id in self.pending:
233
# put a revision into the graph.
234
self._graph_revision(rev_id)
236
ordered_rev_ids = topological_sort(self._rev_graph.items())
237
self._work_queue = [(rev_id, self._rev_graph[rev_id]) for
238
rev_id in ordered_rev_ids]
185
240
# we have topological order of revisions and non ghost parents ready.
186
241
while self._work_queue:
187
242
rev_id, parents = self._work_queue.pop(0)
218
273
mutter('found ghost %s', parent)
219
274
self._rev_graph[rev_id] = parents
221
def _stack_revision(self, rev_id, parents):
222
"""Add rev_id to the pending revision stack."""
223
self._rev_stack.append(rev_id)
224
self._parent_stack.append(parents)
225
self._pending_stack.append(list(parents))
227
def _unstack_revision(self):
228
"""A revision has been completed.
230
The revision is added to the work queue, and the data for
231
it popped from the call stack.
233
rev_id = self._rev_stack.pop()
234
parents = self._parent_stack.pop()
235
self._pending_stack.pop()
236
self._work_queue.append((rev_id, parents))
237
self._work_set.add(rev_id)
238
# and remove it from the rev graph as its now complete
239
self._rev_graph.pop(rev_id)
241
276
def _reweave_step(self, message):
242
277
"""Mark a single step of regeneration complete."""
243
278
self.pb.update(message, self.count, self.total)