240
240
# information you have so far.
241
241
# 5) Continue searching, stopping the common searches when the search
242
242
# tip is an ancestor of all unique nodes.
243
# 6) Search is done when all common searchers have completed.
243
# 6) Aggregate together unique searchers when they are searching the
244
# same tips. When all unique searchers are searching the same node,
245
# stop move it to a single 'all_unique_searcher'.
246
# 7) The 'all_unique_searcher' represents the very 'tip' of searching.
247
# Most of the time this produces very little important information.
248
# So don't step it as quickly as the other searchers.
249
# 8) Search is done when all common searchers have completed.
251
unique_searcher, common_searcher = self._find_initial_unique_nodes(
252
[unique_revision], common_revisions)
254
unique_nodes = unique_searcher.seen.difference(common_searcher.seen)
258
(all_unique_searcher,
259
unique_tip_searchers) = self._make_unique_searchers(unique_nodes,
260
unique_searcher, common_searcher)
262
self._refine_unique_nodes(unique_searcher, all_unique_searcher,
263
unique_tip_searchers, common_searcher)
264
true_unique_nodes = unique_nodes.difference(common_searcher.seen)
245
265
if 'graph' in debug.debug_flags:
246
_mutter = trace.mutter
248
def _mutter(*args, **kwargs):
251
unique_searcher = self._make_breadth_first_searcher([unique_revision])
252
# we know that unique_revision isn't in common_revisions
266
trace.mutter('Found %s truly unique nodes out of %s',
267
len(true_unique_nodes), len(unique_nodes))
268
return true_unique_nodes
270
def _find_initial_unique_nodes(self, unique_revisions, common_revisions):
271
"""Steps 1-3 of find_unique_ancestors.
273
Find the maximal set of unique nodes. Some of these might actually
274
still be common, but we are sure that there are no other unique nodes.
276
:return: (unique_searcher, common_searcher)
279
unique_searcher = self._make_breadth_first_searcher(unique_revisions)
280
# we know that unique_revisions aren't in common_revisions, so skip
253
282
unique_searcher.next()
254
283
common_searcher = self._make_breadth_first_searcher(common_revisions)
285
# As long as we are still finding unique nodes, keep searching
256
286
while unique_searcher._next_query:
257
287
next_unique_nodes = set(unique_searcher.step())
258
288
next_common_nodes = set(common_searcher.step())
264
294
unique_are_common_nodes.update(
265
295
next_common_nodes.intersection(unique_searcher.seen))
266
296
if unique_are_common_nodes:
297
ancestors = unique_searcher.find_seen_ancestors(
298
unique_are_common_nodes)
267
299
# TODO: This is a bit overboard, we only really care about
268
300
# the ancestors of the tips because the rest we
269
301
# already know. This is *correct* but causes us to
270
# search far too much ancestry.
271
ancestors = unique_searcher.find_seen_ancestors(
272
unique_are_common_nodes)
302
# search too much ancestry.
273
303
ancestors.update(common_searcher.find_seen_ancestors(ancestors))
274
304
unique_searcher.stop_searching_any(ancestors)
275
305
common_searcher.start_searching(ancestors)
277
unique_nodes = unique_searcher.seen.difference(common_searcher.seen)
307
return unique_searcher, common_searcher
310
def _make_unique_searchers(self, unique_nodes, unique_searcher,
312
"""Create a searcher for all the unique search tips (step 4).
314
As a side effect, the common_searcher will stop searching any nodes
315
that are ancestors of the unique searcher tips.
317
:return: (all_unique_searcher, unique_tip_searchers)
280
319
unique_tips = self._remove_simple_descendants(unique_nodes,
281
320
self.get_parent_map(unique_nodes))
283
322
if len(unique_tips) == 1:
284
unique_searchers = []
323
unique_tip_searchers = []
285
324
ancestor_all_unique = unique_searcher.find_seen_ancestors(unique_tips)
287
unique_searchers = []
326
unique_tip_searchers = []
288
327
for tip in unique_tips:
289
328
revs_to_search = unique_searcher.find_seen_ancestors([tip])
290
329
revs_to_search.update(
313
355
common_searcher.find_seen_ancestors(ancestor_all_unique))
315
357
total_stopped = 0
316
for searcher in unique_searchers:
358
for searcher in unique_tip_searchers:
317
359
total_stopped += len(searcher.stop_searching_any(
318
360
searcher.find_seen_ancestors(ancestor_all_unique)))
319
_mutter('For %s unique nodes, created %s + 1 unique searchers'
320
' (%s stopped search tips, %s common ancestors'
321
' (%s stopped common)',
322
len(unique_nodes), len(unique_searchers), total_stopped,
323
len(ancestor_all_unique), len(stopped_common))
324
del ancestor_all_unique, stopped_common
326
# We step the ancestor_all_unique searcher only every other step
361
if 'graph' in debug.debug_flags:
362
trace.mutter('For %s unique nodes, created %s + 1 unique searchers'
363
' (%s stopped search tips, %s common ancestors'
364
' (%s stopped common)',
365
len(unique_nodes), len(unique_tip_searchers),
366
total_stopped, len(ancestor_all_unique),
368
return all_unique_searcher, unique_tip_searchers
370
def _step_unique_and_common_searchers(self, common_searcher,
371
unique_tip_searchers,
373
newly_seen_common = set(common_searcher.step())
374
newly_seen_unique = set()
375
for searcher in unique_tip_searchers:
376
next = set(searcher.step())
377
next.update(unique_searcher.find_seen_ancestors(next))
378
next.update(common_searcher.find_seen_ancestors(next))
379
for alt_searcher in unique_tip_searchers:
380
if alt_searcher is searcher:
382
next.update(alt_searcher.find_seen_ancestors(next))
383
searcher.start_searching(next)
384
newly_seen_unique.update(next)
385
return newly_seen_common, newly_seen_unique,
387
def _find_nodes_common_to_all_unique(self, unique_tip_searchers,
389
newly_seen_unique, step_all_unique):
390
"""Find nodes that are common to all unique_tip_searchers.
392
If it is time, step the all_unique_searcher, and add its nodes to the
395
unique_are_common_nodes = newly_seen_unique.copy()
396
for searcher in unique_tip_searchers:
397
unique_are_common_nodes.intersection_update(searcher.seen)
398
unique_are_common_nodes.intersection_update(
399
all_unique_searcher.seen)
400
# Step all-unique less frequently than the other searchers.
401
# In the common case, we don't need to spider out far here, so
402
# avoid doing extra work.
403
if not step_all_unique:
404
tstart = time.clock()
405
nodes = all_unique_searcher.step()
406
unique_are_common_nodes.update(nodes)
407
tdelta = time.clock() - tstart
408
if 'graph' in debug.debug_flags:
409
trace.mutter('all_unique_searcher step() took %.3fs'
410
'for %d nodes (%d total), iteration: %s',
411
tdelta, len(nodes), len(all_unique_searcher.seen),
412
all_unique_searcher._iterations)
413
return unique_are_common_nodes
415
def _collapse_unique_searchers(self, unique_tip_searchers,
416
unique_are_common_nodes):
417
"""Combine searchers that are searching the same tips.
419
When two searchers are searching the same tips, we can stop one of the
420
searchers. We also know that the maximal set of common ancestors is the
421
intersection of the two original searchers.
423
:return: A list of searchers that are searching unique nodes.
425
# Filter out searchers that don't actually search different
426
# nodes. We already have the ancestry intersection for them
427
unique_search_tips = {}
428
for searcher in unique_tip_searchers:
429
stopped = searcher.stop_searching_any(unique_are_common_nodes)
430
will_search_set = frozenset(searcher._next_query)
431
if not will_search_set:
432
if 'graph' in debug.debug_flags:
433
trace.mutter('Unique searcher %s was stopped.'
434
' (%s iterations) %d nodes stopped',
436
searcher._iterations,
438
elif will_search_set not in unique_search_tips:
439
# This searcher is searching a unique set of nodes, let it
440
unique_search_tips[will_search_set] = [searcher]
442
unique_search_tips[will_search_set].append(searcher)
443
# TODO: it might be possible to collapse searchers faster when they
444
# only have *some* search tips in common.
445
next_unique_searchers = []
446
for searchers in unique_search_tips.itervalues():
447
if len(searchers) == 1:
448
# Searching unique tips, go for it
449
next_unique_searchers.append(searchers[0])
451
# These searchers have started searching the same tips, we
452
# don't need them to cover the same ground. The
453
# intersection of their ancestry won't change, so create a
454
# new searcher, combining their histories.
455
next_searcher = searchers[0]
456
for searcher in searchers[1:]:
457
next_searcher.seen.intersection_update(searcher.seen)
458
if 'graph' in debug.debug_flags:
459
trace.mutter('Combining %s searchers into a single'
460
' searcher searching %s nodes with'
463
len(next_searcher._next_query),
464
len(next_searcher.seen))
465
next_unique_searchers.append(next_searcher)
466
return next_unique_searchers
468
def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,
469
unique_tip_searchers, common_searcher):
470
"""Steps 5-8 of find_unique_ancestors.
472
This function returns when common_searcher has stopped searching for
475
# We step the ancestor_all_unique searcher only every
476
# STEP_UNIQUE_SEARCHER_EVERY steps.
477
step_all_unique_counter = 0
328
478
# While we still have common nodes to search
329
479
while common_searcher._next_query:
330
newly_seen_common = set(common_searcher.step())
331
newly_seen_unique = set()
332
def step_searchers():
333
for searcher in unique_searchers:
334
next = set(searcher.step())
335
next.update(unique_searcher.find_seen_ancestors(next))
336
next.update(common_searcher.find_seen_ancestors(next))
337
for alt_searcher in unique_searchers:
338
if alt_searcher is searcher:
340
next.update(alt_searcher.find_seen_ancestors(next))
341
searcher.start_searching(next)
342
newly_seen_unique.update(next)
481
newly_seen_unique) = self._step_unique_and_common_searchers(
482
common_searcher, unique_tip_searchers, unique_searcher)
344
483
# These nodes are common ancestors of all unique nodes
345
unique_are_common_nodes = newly_seen_unique.copy()
346
def intersect_searchers():
347
for searcher in unique_searchers:
348
unique_are_common_nodes.intersection_update(searcher.seen)
349
unique_are_common_nodes.intersection_update(
350
all_unique_searcher.seen)
351
# Step the all-unique less frequently than the other searchers.
352
# In the common case, we don't need to spider out far here, so
353
# avoid doing extra work.
354
if not step_all_unique:
355
tstart = time.clock()
356
nodes = all_unique_searcher.step()
357
unique_are_common_nodes.update(nodes)
358
tdelta = time.clock() - tstart
359
_mutter('all_unique_searcher step() took %.3fs for %d nodes'
360
' (%d total), iteration: %s', tdelta,
361
len(nodes), len(all_unique_searcher.seen),
362
all_unique_searcher._iterations)
363
intersect_searchers()
364
step_all_unique = (step_all_unique + 1) % STEP_UNIQUE_SEARCHER_EVERY
484
unique_are_common_nodes = self._find_nodes_common_to_all_unique(
485
unique_tip_searchers, all_unique_searcher, newly_seen_unique,
486
step_all_unique_counter==0)
487
step_all_unique_counter = ((step_all_unique_counter + 1)
488
% STEP_UNIQUE_SEARCHER_EVERY)
366
490
if newly_seen_common:
367
491
# If a 'common' node is an ancestor of all unique searchers, we
376
500
all_unique_searcher.start_searching(unique_are_common_nodes)
377
501
common_searcher.stop_searching_any(unique_are_common_nodes)
379
def filter_searchers():
380
# Filter out searchers that don't actually search different
381
# nodes. We already have the ancestry intersection for them
382
unique_search_tips = {}
383
for searcher in unique_searchers:
384
stopped = searcher.stop_searching_any(unique_are_common_nodes)
385
will_search_set = frozenset(searcher._next_query)
386
if not will_search_set:
387
_mutter('Unique searcher %s was stopped.'
388
' (%s iterations) %d nodes stopped',
390
searcher._iterations,
392
elif will_search_set not in unique_search_tips:
393
# This searcher is searching a unique set of nodes, let it
394
unique_search_tips[will_search_set] = [searcher]
396
unique_search_tips[will_search_set].append(searcher)
397
next_unique_searchers = []
398
for searchers in unique_search_tips.itervalues():
399
if len(searchers) == 1:
400
# Searching unique tips, go for it
401
next_unique_searchers.append(searchers[0])
403
# These searchers have started searching the same tips, we
404
# don't need them to cover the same ground. The
405
# intersection of their ancestry won't change, so create a
406
# new searcher, combining their histories.
407
next_searcher = searchers[0]
408
ancestor_intersection = next_searcher.seen
409
for searcher in searchers[1:]:
410
ancestor_intersection = ancestor_intersection.intersection(searcher.seen)
411
next_searcher.seen = ancestor_intersection
412
_mutter('Combining %s searchers into a single searcher'
413
' searching %s nodes with %s ancestry',
414
len(searchers), len(next_searcher._next_query),
415
len(next_searcher.seen))
416
next_unique_searchers.append(next_searcher)
417
return next_unique_searchers
418
next_unique_searchers = filter_searchers()
419
if len(unique_searchers) != len(next_unique_searchers):
420
_mutter('Collapsed %s unique searchers => %s'
422
len(unique_searchers), len(next_unique_searchers),
423
all_unique_searcher._iterations)
424
unique_searchers = next_unique_searchers
425
true_unique_nodes = unique_nodes.difference(common_searcher.seen)
426
_mutter('Found %s truly unique nodes out of %s',
427
len(true_unique_nodes), len(unique_nodes))
428
return true_unique_nodes
503
next_unique_searchers = self._collapse_unique_searchers(
504
unique_tip_searchers, unique_are_common_nodes)
505
if len(unique_tip_searchers) != len(next_unique_searchers):
506
if 'graph' in debug.debug_flags:
507
trace.mutter('Collapsed %s unique searchers => %s'
509
len(unique_tip_searchers),
510
len(next_unique_searchers),
511
all_unique_searcher._iterations)
512
unique_tip_searchers = next_unique_searchers
430
514
@symbol_versioning.deprecated_method(symbol_versioning.one_one)
431
515
def get_parents(self, revisions):