191
def revision_graph(revision, revision_source):
192
"""Produce a graph of the ancestry of the specified revision.
194
:return: root, ancestors map, descendants map
196
revision_source.lock_read()
198
return _revision_graph(revision, revision_source)
200
revision_source.unlock()
203
def _revision_graph(revision, revision_source):
204
"""See revision_graph."""
205
from bzrlib.tsort import topo_sort
206
graph = revision_source.get_revision_graph(revision)
207
# mark all no-parent revisions as being NULL_REVISION parentage.
208
for node, parents in graph.items():
209
if len(parents) == 0:
210
graph[node] = [NULL_REVISION]
211
# add NULL_REVISION to the graph
212
graph[NULL_REVISION] = []
214
# pick a root. If there are multiple roots
215
# this could pick a random one.
216
topo_order = topo_sort(graph.items())
222
# map the descendants of the graph.
223
# and setup our set based return graph.
224
for node in graph.keys():
225
descendants[node] = {}
226
for node, parents in graph.items():
227
for parent in parents:
228
descendants[parent][node] = 1
229
ancestors[node] = set(parents)
231
assert root not in descendants[root]
232
assert root not in ancestors[root]
233
return root, ancestors, descendants
236
def combined_graph(revision_a, revision_b, revision_source):
237
"""Produce a combined ancestry graph.
238
Return graph root, ancestors map, descendants map, set of common nodes"""
239
root, ancestors, descendants = revision_graph(
240
revision_a, revision_source)
241
root_b, ancestors_b, descendants_b = revision_graph(
242
revision_b, revision_source)
244
raise errors.NoCommonRoot(revision_a, revision_b)
246
for node, node_anc in ancestors_b.iteritems():
247
if node in ancestors:
250
ancestors[node] = set()
251
ancestors[node].update(node_anc)
252
for node, node_dec in descendants_b.iteritems():
253
if node not in descendants:
254
descendants[node] = {}
255
descendants[node].update(node_dec)
256
return root, ancestors, descendants, common
259
def common_ancestor(revision_a, revision_b, revision_source,
261
if None in (revision_a, revision_b):
263
if NULL_REVISION in (revision_a, revision_b):
265
# trivial optimisation
266
if revision_a == revision_b:
270
pb.update('Picking ancestor', 1, 3)
271
graph = revision_source.get_revision_graph_with_ghosts(
272
[revision_a, revision_b])
273
# Shortcut the case where one of the tips is already included in
274
# the other graphs ancestry.
275
ancestry_a = graph.get_ancestry(revision_a, topo_sorted=False)
276
if revision_b in ancestry_a:
278
ancestry_b = graph.get_ancestry(revision_b, topo_sorted=False)
279
if revision_a in ancestry_b:
281
# convert to a NULL_REVISION based graph.
282
ancestors = graph.get_ancestors()
283
descendants = graph.get_descendants()
284
common = set(ancestry_a)
285
common.intersection_update(ancestry_b)
286
descendants[NULL_REVISION] = {}
287
ancestors[NULL_REVISION] = []
288
for root in graph.roots:
289
descendants[NULL_REVISION][root] = 1
290
ancestors[root].append(NULL_REVISION)
291
for ghost in graph.ghosts:
292
# ghosts act as roots for the purpose of finding
293
# the longest paths from the root: any ghost *might*
294
# be directly attached to the root, so we treat them
296
# ghost now descends from NULL
297
descendants[NULL_REVISION][ghost] = 1
298
# that is it has an ancestor of NULL
299
ancestors[ghost] = [NULL_REVISION]
300
# ghost is common if any of ghosts descendants are common:
301
for ghost_descendant in descendants[ghost]:
302
if ghost_descendant in common:
306
common.add(NULL_REVISION)
307
except errors.NoCommonRoot:
308
raise errors.NoCommonAncestor(revision_a, revision_b)
310
pb.update('Picking ancestor', 2, 3)
311
distances = node_distances (descendants, ancestors, root)
312
pb.update('Picking ancestor', 3, 2)
313
farthest = select_farthest(distances, common)
314
if farthest is None or farthest == NULL_REVISION:
315
raise errors.NoCommonAncestor(revision_a, revision_b)
321
class MultipleRevisionSources(object):
322
"""Proxy that looks in multiple branches for revisions."""
323
def __init__(self, *args):
324
object.__init__(self)
325
assert len(args) != 0
326
self._revision_sources = args
328
def revision_parents(self, revision_id):
329
for source in self._revision_sources:
331
return source.revision_parents(revision_id)
332
except (errors.WeaveRevisionNotPresent, errors.NoSuchRevision), e:
336
def get_revision(self, revision_id):
337
for source in self._revision_sources:
339
return source.get_revision(revision_id)
340
except errors.NoSuchRevision, e:
344
def get_revision_graph(self, revision_id):
345
# we could probe incrementally until the pending
346
# ghosts list stop growing, but its cheaper for now
347
# to just ask for the complete graph for each repository.
349
for source in self._revision_sources:
350
ghost_graph = source.get_revision_graph_with_ghosts()
351
graphs.append(ghost_graph)
354
if not revision_id in graph.get_ancestors():
356
if absent == len(graphs):
357
raise errors.NoSuchRevision(self._revision_sources[0], revision_id)
361
pending = set([revision_id])
362
def find_parents(node_id):
363
"""find the parents for node_id."""
365
ancestors = graph.get_ancestors()
367
return ancestors[node_id]
370
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
372
# all the graphs should have identical parent lists
373
node_id = pending.pop()
375
result[node_id] = find_parents(node_id)
376
for parent_node in result[node_id]:
377
if not parent_node in result:
378
pending.add(parent_node)
379
except errors.NoSuchRevision:
384
def get_revision_graph_with_ghosts(self, revision_ids):
385
# query all the sources for their entire graphs
386
# and then build a combined graph for just
389
for source in self._revision_sources:
390
ghost_graph = source.get_revision_graph_with_ghosts()
391
graphs.append(ghost_graph.get_ancestors())
392
for revision_id in revision_ids:
395
if not revision_id in graph:
397
if absent == len(graphs):
398
raise errors.NoSuchRevision(self._revision_sources[0],
403
pending = set(revision_ids)
405
def find_parents(node_id):
406
"""find the parents for node_id."""
409
return graph[node_id]
412
raise errors.NoSuchRevision(self._revision_sources[0], node_id)
414
# all the graphs should have identical parent lists
415
node_id = pending.pop()
417
parents = find_parents(node_id)
418
for parent_node in parents:
420
if (parent_node not in pending and
421
parent_node not in done):
423
pending.add(parent_node)
424
result.add_node(node_id, parents)
426
except errors.NoSuchRevision:
428
result.add_ghost(node_id)
433
for source in self._revision_sources:
437
for source in self._revision_sources:
167
441
def is_reserved_id(revision_id):
168
442
"""Determine whether a revision id is reserved