298
288
yield revision_id, str(start_revno - num), 0
301
def _iter_revisions(repository, view_revisions, generate_delta):
291
def make_log_rev_iterator(branch, view_revisions, generate_delta, search):
292
"""Create a revision iterator for log.
294
:param branch: The branch being logged.
295
:param view_revisions: The revisions being viewed.
296
:param generate_delta: Whether to generate a delta for each revision.
297
:param search: A user text search string.
298
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
301
# Convert view_revisions into (view, None, None) groups to fit with
302
# the standard interface here.
303
if type(view_revisions) == list:
304
# A single batch conversion is faster than many incremental ones.
305
# As we have all the data, do a batch conversion.
306
nones = [None] * len(view_revisions)
307
log_rev_iterator = iter([zip(view_revisions, nones, nones)])
310
for view in view_revisions:
311
yield (view, None, None)
312
log_rev_iterator = iter([_convert()])
313
for adapter in log_adapters:
314
log_rev_iterator = adapter(branch, generate_delta, search,
316
return log_rev_iterator
319
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
320
"""Create a filtered iterator of log_rev_iterator matching on a regex.
322
:param branch: The branch being logged.
323
:param generate_delta: Whether to generate a delta for each revision.
324
:param search: A user text search string.
325
:param log_rev_iterator: An input iterator containing all revisions that
326
could be displayed, in lists.
327
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
331
return log_rev_iterator
332
# Compile the search now to get early errors.
333
searchRE = re.compile(search, re.IGNORECASE)
334
return _filter_message_re(searchRE, log_rev_iterator)
337
def _filter_message_re(searchRE, log_rev_iterator):
338
for revs in log_rev_iterator:
340
for (rev_id, revno, merge_depth), rev, delta in revs:
341
if searchRE.search(rev.message):
342
new_revs.append(((rev_id, revno, merge_depth), rev, delta))
346
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator):
347
"""Add revision deltas to a log iterator if needed.
349
:param branch: The branch being logged.
350
:param generate_delta: Whether to generate a delta for each revision.
351
:param search: A user text search string.
352
:param log_rev_iterator: An input iterator containing all revisions that
353
could be displayed, in lists.
354
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
357
if not generate_delta:
358
return log_rev_iterator
359
return _generate_deltas(branch.repository, log_rev_iterator)
362
def _generate_deltas(repository, log_rev_iterator):
363
"""Create deltas for each batch of revisions in log_rev_iterator."""
364
for revs in log_rev_iterator:
365
revisions = [rev[1] for rev in revs]
366
deltas = repository.get_deltas_for_revisions(revisions)
367
revs = [(rev[0], rev[1], delta) for rev, delta in izip(revs, deltas)]
371
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
372
"""Extract revision objects from the repository
374
:param branch: The branch being logged.
375
:param generate_delta: Whether to generate a delta for each revision.
376
:param search: A user text search string.
377
:param log_rev_iterator: An input iterator containing all revisions that
378
could be displayed, in lists.
379
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
382
repository = branch.repository
383
for revs in log_rev_iterator:
384
# r = revision_id, n = revno, d = merge depth
385
revision_ids = [view[0] for view, _, _ in revs]
386
revisions = repository.get_revisions(revision_ids)
387
revs = [(rev[0], revision, rev[2]) for rev, revision in
388
izip(revs, revisions)]
392
def _make_batch_filter(branch, generate_delta, search, log_rev_iterator):
393
"""Group up a single large batch into smaller ones.
395
:param branch: The branch being logged.
396
:param generate_delta: Whether to generate a delta for each revision.
397
:param search: A user text search string.
398
:param log_rev_iterator: An input iterator containing all revisions that
399
could be displayed, in lists.
400
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev, delta).
402
repository = branch.repository
303
view_revisions = iter(view_revisions)
305
cur_view_revisions = [d for x, d in zip(range(num), view_revisions)]
306
if len(cur_view_revisions) == 0:
309
# r = revision, n = revno, d = merge depth
310
revision_ids = [r for (r, n, d) in cur_view_revisions]
311
revisions = repository.get_revisions(revision_ids)
313
deltas = repository.get_deltas_for_revisions(revisions)
314
cur_deltas = dict(izip((r.revision_id for r in revisions),
316
for view_data, revision in izip(cur_view_revisions, revisions):
317
yield view_data, revision, cur_deltas.get(revision.revision_id)
318
num = min(int(num * 1.5), 200)
404
for batch in log_rev_iterator:
407
step = [detail for _, detail in zip(range(num), batch)]
411
num = min(int(num * 1.5), 200)
321
414
def _get_mainline_revs(branch, start_revision, end_revision):
443
536
return view_revisions
446
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
448
"""Return the list of revision ids which touch a given file id.
539
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
541
r"""Return the list of revision ids which touch a given file id.
450
543
The function filters view_revisions and returns a subset.
451
544
This includes the revisions which directly change the file id,
452
545
and the revisions which merge these changes. So if the
453
546
revision graph is::
460
And 'C' changes a file, then both C and D will be returned.
462
This will also can be restricted based on a subset of the mainline.
557
And 'C' changes a file, then both C and D will be returned. F will not be
558
returned even though it brings the changes to C into the branch starting
559
with E. (Note that if we were using F as the tip instead of G, then we
562
This will also be restricted based on a subset of the mainline.
564
:param branch: The branch where we can get text revision information.
565
:param file_id: Filter out revisions that do not touch file_id.
566
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
567
tuples. This is the list of revisions which will be filtered. It is
568
assumed that view_revisions is in merge_sort order (either forward or
570
:param direction: The direction of view_revisions. See also
571
reverse_by_depth, and get_view_revisions
464
572
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
466
# find all the revisions that change the specific file
467
file_weave = branch.repository.weave_store.get_weave(file_id,
468
branch.repository.get_transaction())
469
weave_modifed_revisions = set(file_weave.versions())
470
# build the ancestry of each revision in the graph
471
# - only listing the ancestors that change the specific file.
472
graph = branch.repository.get_graph()
473
# This asks for all mainline revisions, which means we only have to spider
474
# sideways, rather than depth history. That said, its still size-of-history
475
# and should be addressed.
476
# mainline_revisions always includes an extra revision at the beginning, so
478
parent_map = dict(((key, value) for key, value in
479
graph.iter_ancestry(mainline_revisions[1:]) if value is not None))
480
sorted_rev_list = topo_sort(parent_map.items())
482
for rev in sorted_rev_list:
483
parents = parent_map[rev]
484
if rev not in weave_modifed_revisions and len(parents) == 1:
485
# We will not be adding anything new, so just use a reference to
486
# the parent ancestry.
487
rev_ancestry = ancestry[parents[0]]
574
# Lookup all possible text keys to determine which ones actually modified
576
text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
577
# Looking up keys in batches of 1000 can cut the time in half, as well as
578
# memory consumption. GraphIndex *does* like to look for a few keys in
579
# parallel, it just doesn't like looking for *lots* of keys in parallel.
580
# TODO: This code needs to be re-evaluated periodically as we tune the
581
# indexing layer. We might consider passing in hints as to the known
582
# access pattern (sparse/clustered, high success rate/low success
583
# rate). This particular access is clustered with a low success rate.
584
get_parent_map = branch.repository.texts.get_parent_map
585
modified_text_revisions = set()
587
for start in xrange(0, len(text_keys), chunk_size):
588
next_keys = text_keys[start:start + chunk_size]
589
# Only keep the revision_id portion of the key
590
modified_text_revisions.update(
591
[k[1] for k in get_parent_map(next_keys)])
592
del text_keys, next_keys
595
if direction == 'forward':
596
# TODO: The algorithm for finding 'merges' of file changes expects
597
# 'reverse' order (the default from 'merge_sort()'). Instead of
598
# forcing this, we could just use the reverse_by_depth order.
599
view_revisions = reverse_by_depth(view_revisions)
600
# Track what revisions will merge the current revision, replace entries
601
# with 'None' when they have been added to result
602
current_merge_stack = [None]
603
for info in view_revisions:
604
rev_id, revno, depth = info
605
if depth == len(current_merge_stack):
606
current_merge_stack.append(info)
490
if rev in weave_modifed_revisions:
491
rev_ancestry.add(rev)
492
for parent in parents:
493
if parent not in ancestry:
494
# parent is a Ghost, which won't be present in
495
# sorted_rev_list, but we may access it later, so create an
497
ancestry[parent] = set()
498
rev_ancestry = rev_ancestry.union(ancestry[parent])
499
ancestry[rev] = rev_ancestry
501
def is_merging_rev(r):
502
parents = parent_map[r]
504
leftparent = parents[0]
505
for rightparent in parents[1:]:
506
if not ancestry[leftparent].issuperset(
507
ancestry[rightparent]):
511
# filter from the view the revisions that did not change or merge
513
return [(r, n, d) for r, n, d in view_revs_iter
514
if r in weave_modifed_revisions or is_merging_rev(r)]
608
del current_merge_stack[depth + 1:]
609
current_merge_stack[-1] = info
611
if rev_id in modified_text_revisions:
612
# This needs to be logged, along with the extra revisions
613
for idx in xrange(len(current_merge_stack)):
614
node = current_merge_stack[idx]
617
current_merge_stack[idx] = None
618
if direction == 'forward':
619
result = reverse_by_depth(result)
517
623
def get_view_revisions(mainline_revs, rev_nos, branch, direction,