87
87
# the total set of revisions to process
88
88
self.pending = set([file_id for file_id in self.repo.revision_store])
89
91
# the current line of history being processed.
90
92
# once we add in per inventory root ids this will involve a single
91
93
# pass within the revision knit at that point.
92
# this contains (revision_id, not_ghost_parents) tuples.
94
# these contain respectively the revision id and the not_ghost_parents.
93
95
self._rev_stack = []
96
self._parent_stack = []
97
self._pending_stack = []
98
# actual queue to reinsert. This is a single pass queue we can use to
99
# regenerate the inventory versioned file, and holds
100
# (rev_id, non_ghost_parents) entries
101
self._work_queue = []
102
# this is a set for fast lookups of queued revisions
103
self._work_set = set()
94
104
# total steps = 1 read per revision + one insert into the inventory
95
105
self.total = len(self.pending) * 2
98
# outer loop to ensure we hit every revision
100
self._stack_revision()
101
# now we have one revision on the todo stack.
102
# try to process the tip of the stack and if we cant
103
# insert it yet queue the revision ids we need to be
104
# able to add it. Rinse and repeat until the stack is
105
# empty and then we grab another pending revision if
107
while len(self._rev_stack):
108
rev_id, parents = self._rev_stack[-1]
109
unavailable = [p for p in parents if p not in new_inventory]
110
if len(unavailable) == 0:
111
# this entry can be popped off.
112
self.pb.update('regenerating', self.count, self.total)
114
new_inventory.add(rev_id, parents, self.inventory.get(rev_id))
115
self._rev_stack.pop()
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
# now we do a depth first search of the revision graph until its empty.
117
# this gives us a topological order across all revisions in
118
# self._all_revisions.
119
# This is flattened to avoid deep recursion:
120
# At each step in the call graph we need to know which parent we are on.
121
# we do this by having three variables for each stack frame:
122
# revision_id being descended into (self._rev_stack)
123
# parents list for using when regenerating that revision_id's inventory
124
# (self._parent_stack)
125
# current queue of parents to descend into
126
# (self._pending_stack)
127
# putting all the parents into a pending list, left to
128
# right and processing the right most one each time. The same parent
129
# may occur at multiple places in the stack - thats fine. We check its
130
# not in the output before processing. However revisions cannot
132
# the algorithm is roughly:
133
# for revision, parents in _rev_graph.items():
134
# if revision in self._all_revisions:
136
# add_revision_parents(revision, parents)
137
# def add_revision_parents(revision, parents)
138
# fpr parent in parents:
139
# add_revision_parents(parent, parent.parents)
140
# self._all_revisions.append(revision)
141
# self._all_revision_parents.appent(parents)
142
# however we tune this to visit fragmens of the graph
143
# and not double-visit entries.
144
# nevertheless the output is a
145
# self._work_queue of revisions, nonghost parents in
146
# topological order and
147
# a self._work_set which is a complete set of revisions.
148
while self._rev_graph:
149
rev_id, parents = self._rev_graph.iteritems().next()
151
self._stack_revision(rev_id, parents)
152
while self._rev_stack:
153
# loop until this call completes.
154
parents_to_visit = self._pending_stack[-1]
155
# if all parents are done, the revision is done
156
if not parents_to_visit:
157
# append the revision to the topo sorted list
158
self._unstack_revision()
117
# push the needed parents onto the stack
118
for parent in unavailable:
119
self._stack_revision(parent)
160
while self._pending_stack[-1]:
161
# recurse depth first into a single parent
162
next_rev_id = self._pending_stack[-1].pop()
163
if next_rev_id in self._work_set:
164
# this parent was completed by a child on the
165
# call stack. skip it.
167
# push it into the call stack
168
self._stack_revision(next_rev_id, self._rev_graph[next_rev_id])
169
# and do not continue processing parents until this 'call'
173
# we are now recursing down a call stack.
174
# its a width first search which
176
### Useful if fiddling with this code.
178
### for index in range(len(self._work_queue)):
179
### rev = self._work_queue[index][0]
180
### for left_index in range(index):
181
### if rev in self._work_queue[left_index][1]:
182
### print "revision in parent list of earlier revision"
183
### import pdb;pdb.set_trace()
185
# we have topological order of revisions and non ghost parents ready.
186
while self._work_queue:
187
rev_id, parents = self._work_queue.pop(0)
188
# double check this really is in topological order.
189
unavailable = [p for p in parents if p not in new_inventory]
190
assert len(unavailable) == 0
191
# this entry has all the non ghost parents in the inventory
193
self._reweave_step('adding inventories')
194
new_inventory.add(rev_id, parents, self.inventory.get(rev_id))
196
# if this worked, the set of new_inventory.names should equal
198
assert set(new_inventory.names()) == self.pending
120
199
self.pb.update('Writing weave')
121
200
self.repo.control_weaves.put_weave('inventory',
139
216
parents.append(parent)
141
218
mutter('found ghost %s', parent)
142
self._rev_stack.append((rev_id, parents))
219
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
def _reweave_step(self, message):
242
"""Mark a single step of regeneration complete."""
243
self.pb.update(message, self.count, self.total)