221
228
# now we just print all the revisions
223
revision_iterator = make_log_rev_iterator(branch, view_revisions,
224
generate_delta, search)
225
for revs in revision_iterator:
226
for (rev_id, revno, merge_depth), rev, delta in revs:
227
lr = LogRevision(rev, revno, merge_depth, delta,
228
rev_tag_dict.get(rev_id))
232
if log_count >= limit:
230
for (rev_id, revno, merge_depth), rev, delta in _iter_revisions(
231
branch.repository, view_revisions, generate_delta):
233
if not searchRE.search(rev.message):
236
lr = LogRevision(rev, revno, merge_depth, delta,
237
rev_tag_dict.get(rev_id))
241
if log_count >= limit:
236
245
def calculate_view_revisions(branch, start_revision, end_revision, direction,
237
246
specific_fileid, generate_merge_revisions,
238
247
allow_single_merge_revision):
239
if ( not generate_merge_revisions
240
and start_revision is end_revision is None
241
and direction == 'reverse'
242
and specific_fileid is None):
248
if (not generate_merge_revisions and start_revision is end_revision is
249
None and direction == 'reverse' and specific_fileid is None):
243
250
return _linear_view_revisions(branch)
245
mainline_revs, rev_nos, start_rev_id, end_rev_id = _get_mainline_revs(
246
branch, start_revision, end_revision)
252
mainline_revs, rev_nos, start_rev_id, end_rev_id = \
253
_get_mainline_revs(branch, start_revision, end_revision)
247
254
if not mainline_revs:
257
if direction == 'reverse':
258
start_rev_id, end_rev_id = end_rev_id, start_rev_id
250
260
generate_single_revision = False
251
261
if ((not generate_merge_revisions)
252
262
and ((start_rev_id and (start_rev_id not in rev_nos))
289
297
yield revision_id, str(start_revno - num), 0
292
def make_log_rev_iterator(branch, view_revisions, generate_delta, search):
293
"""Create a revision iterator for log.
295
:param branch: The branch being logged.
296
:param view_revisions: The revisions being viewed.
297
:param generate_delta: Whether to generate a delta for each revision.
298
:param search: A user text search string.
299
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
302
# Convert view_revisions into (view, None, None) groups to fit with
303
# the standard interface here.
304
if type(view_revisions) == list:
305
# A single batch conversion is faster than many incremental ones.
306
# As we have all the data, do a batch conversion.
307
nones = [None] * len(view_revisions)
308
log_rev_iterator = iter([zip(view_revisions, nones, nones)])
311
for view in view_revisions:
312
yield (view, None, None)
313
log_rev_iterator = iter([_convert()])
314
for adapter in log_adapters:
315
log_rev_iterator = adapter(branch, generate_delta, search,
317
return log_rev_iterator
320
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
321
"""Create a filtered iterator of log_rev_iterator matching on a regex.
323
:param branch: The branch being logged.
324
:param generate_delta: Whether to generate a delta for each revision.
325
:param search: A user text search string.
326
:param log_rev_iterator: An input iterator containing all revisions that
327
could be displayed, in lists.
328
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
332
return log_rev_iterator
333
# Compile the search now to get early errors.
334
searchRE = re.compile(search, re.IGNORECASE)
335
return _filter_message_re(searchRE, log_rev_iterator)
338
def _filter_message_re(searchRE, log_rev_iterator):
339
for revs in log_rev_iterator:
341
for (rev_id, revno, merge_depth), rev, delta in revs:
342
if searchRE.search(rev.message):
343
new_revs.append(((rev_id, revno, merge_depth), rev, delta))
347
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator):
348
"""Add revision deltas to a log iterator if needed.
350
:param branch: The branch being logged.
351
:param generate_delta: Whether to generate a delta for each revision.
352
:param search: A user text search string.
353
:param log_rev_iterator: An input iterator containing all revisions that
354
could be displayed, in lists.
355
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
358
if not generate_delta:
359
return log_rev_iterator
360
return _generate_deltas(branch.repository, log_rev_iterator)
363
def _generate_deltas(repository, log_rev_iterator):
364
"""Create deltas for each batch of revisions in log_rev_iterator."""
365
for revs in log_rev_iterator:
366
revisions = [rev[1] for rev in revs]
367
deltas = repository.get_deltas_for_revisions(revisions)
368
revs = [(rev[0], rev[1], delta) for rev, delta in izip(revs, deltas)]
372
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
373
"""Extract revision objects from the repository
375
:param branch: The branch being logged.
376
:param generate_delta: Whether to generate a delta for each revision.
377
:param search: A user text search string.
378
:param log_rev_iterator: An input iterator containing all revisions that
379
could be displayed, in lists.
380
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
383
repository = branch.repository
384
for revs in log_rev_iterator:
385
# r = revision_id, n = revno, d = merge depth
386
revision_ids = [view[0] for view, _, _ in revs]
300
def _iter_revisions(repository, view_revisions, generate_delta):
302
view_revisions = iter(view_revisions)
304
cur_view_revisions = [d for x, d in zip(range(num), view_revisions)]
305
if len(cur_view_revisions) == 0:
308
# r = revision, n = revno, d = merge depth
309
revision_ids = [r for (r, n, d) in cur_view_revisions]
387
310
revisions = repository.get_revisions(revision_ids)
388
revs = [(rev[0], revision, rev[2]) for rev, revision in
389
izip(revs, revisions)]
393
def _make_batch_filter(branch, generate_delta, search, log_rev_iterator):
394
"""Group up a single large batch into smaller ones.
396
:param branch: The branch being logged.
397
:param generate_delta: Whether to generate a delta for each revision.
398
:param search: A user text search string.
399
:param log_rev_iterator: An input iterator containing all revisions that
400
could be displayed, in lists.
401
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev, delta).
403
repository = branch.repository
405
for batch in log_rev_iterator:
408
step = [detail for _, detail in zip(range(num), batch)]
412
num = min(int(num * 1.5), 200)
312
deltas = repository.get_deltas_for_revisions(revisions)
313
cur_deltas = dict(izip((r.revision_id for r in revisions),
315
for view_data, revision in izip(cur_view_revisions, revisions):
316
yield view_data, revision, cur_deltas.get(revision.revision_id)
317
num = min(int(num * 1.5), 200)
415
320
def _get_mainline_revs(branch, start_revision, end_revision):
439
344
# filtered later.
440
345
# Also map the revisions to rev_ids, to be used in the later filtering
443
348
if start_revision is None:
446
if isinstance(start_revision, revisionspec.RevisionInfo):
351
if isinstance(start_revision,RevisionInfo):
447
352
start_rev_id = start_revision.rev_id
448
353
start_revno = start_revision.revno or 1
450
355
branch.check_real_revno(start_revision)
451
356
start_revno = start_revision
453
358
end_rev_id = None
454
359
if end_revision is None:
455
end_revno = branch_revno
360
end_revno = len(which_revs)
457
if isinstance(end_revision, revisionspec.RevisionInfo):
362
if isinstance(end_revision,RevisionInfo):
458
363
end_rev_id = end_revision.rev_id
459
end_revno = end_revision.revno or branch_revno
364
end_revno = end_revision.revno or len(which_revs)
461
366
branch.check_real_revno(end_revision)
462
367
end_revno = end_revision
464
if ((start_rev_id == _mod_revision.NULL_REVISION)
465
or (end_rev_id == _mod_revision.NULL_REVISION)):
466
raise errors.BzrCommandError('Logging revision 0 is invalid.')
369
if ((start_rev_id == NULL_REVISION)
370
or (end_rev_id == NULL_REVISION)):
371
raise BzrCommandError('Logging revision 0 is invalid.')
467
372
if start_revno > end_revno:
468
raise errors.BzrCommandError("Start revision must be older than "
373
raise BzrCommandError("Start revision must be older than "
471
if end_revno < start_revno:
376
# list indexes are 0-based; revisions are 1-based
377
cut_revs = which_revs[(start_revno-1):(end_revno)]
472
379
return None, None, None, None
473
cur_revno = branch_revno
476
for revision_id in branch.repository.iter_reverse_revision_history(
477
branch_last_revision):
478
if cur_revno < start_revno:
479
# We have gone far enough, but we always add 1 more revision
480
rev_nos[revision_id] = cur_revno
481
mainline_revs.append(revision_id)
483
if cur_revno <= end_revno:
484
rev_nos[revision_id] = cur_revno
485
mainline_revs.append(revision_id)
488
# We walked off the edge of all revisions, so we add a 'None' marker
489
mainline_revs.append(None)
491
mainline_revs.reverse()
381
# convert the revision history to a dictionary:
382
rev_nos = dict((k, v) for v, k in cut_revs)
493
384
# override the mainline to look like the revision history.
385
mainline_revs = [revision_id for index, revision_id in cut_revs]
386
if cut_revs[0][0] == 1:
387
mainline_revs.insert(0, None)
389
mainline_revs.insert(0, which_revs[start_revno-2][1])
494
390
return mainline_revs, rev_nos, start_rev_id, end_rev_id
537
433
return view_revisions
540
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.
436
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
438
"""Return the list of revision ids which touch a given file id.
543
440
The function filters view_revisions and returns a subset.
544
441
This includes the revisions which directly change the file id,
545
442
and the revisions which merge these changes. So if the
546
443
revision graph is::
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.
566
:param file_id: Filter out revisions that do not touch file_id.
568
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
569
tuples. This is the list of revisions which will be filtered. It is
570
assumed that view_revisions is in merge_sort order (i.e. newest
450
And 'C' changes a file, then both C and D will be returned.
452
This will also can be restricted based on a subset of the mainline.
573
454
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
575
# Lookup all possible text keys to determine which ones actually modified
577
text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
578
# Looking up keys in batches of 1000 can cut the time in half, as well as
579
# memory consumption. GraphIndex *does* like to look for a few keys in
580
# parallel, it just doesn't like looking for *lots* of keys in parallel.
581
# TODO: This code needs to be re-evaluated periodically as we tune the
582
# indexing layer. We might consider passing in hints as to the known
583
# access pattern (sparse/clustered, high success rate/low success
584
# rate). This particular access is clustered with a low success rate.
585
get_parent_map = branch.repository.texts.get_parent_map
586
modified_text_revisions = set()
588
for start in xrange(0, len(text_keys), chunk_size):
589
next_keys = text_keys[start:start + chunk_size]
590
# Only keep the revision_id portion of the key
591
modified_text_revisions.update(
592
[k[1] for k in get_parent_map(next_keys)])
593
del text_keys, next_keys
596
# Track what revisions will merge the current revision, replace entries
597
# with 'None' when they have been added to result
598
current_merge_stack = [None]
599
for info in view_revisions:
600
rev_id, revno, depth = info
601
if depth == len(current_merge_stack):
602
current_merge_stack.append(info)
456
# find all the revisions that change the specific file
457
file_weave = branch.repository.weave_store.get_weave(file_id,
458
branch.repository.get_transaction())
459
weave_modifed_revisions = set(file_weave.versions())
460
# build the ancestry of each revision in the graph
461
# - only listing the ancestors that change the specific file.
462
rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
463
sorted_rev_list = topo_sort(rev_graph)
465
for rev in sorted_rev_list:
466
parents = rev_graph[rev]
467
if rev not in weave_modifed_revisions and len(parents) == 1:
468
# We will not be adding anything new, so just use a reference to
469
# the parent ancestry.
470
rev_ancestry = ancestry[parents[0]]
604
del current_merge_stack[depth + 1:]
605
current_merge_stack[-1] = info
607
if rev_id in modified_text_revisions:
608
# This needs to be logged, along with the extra revisions
609
for idx in xrange(len(current_merge_stack)):
610
node = current_merge_stack[idx]
613
current_merge_stack[idx] = None
473
if rev in weave_modifed_revisions:
474
rev_ancestry.add(rev)
475
for parent in parents:
476
rev_ancestry = rev_ancestry.union(ancestry[parent])
477
ancestry[rev] = rev_ancestry
479
def is_merging_rev(r):
480
parents = rev_graph[r]
482
leftparent = parents[0]
483
for rightparent in parents[1:]:
484
if not ancestry[leftparent].issuperset(
485
ancestry[rightparent]):
489
# filter from the view the revisions that did not change or merge
491
return [(r, n, d) for r, n, d in view_revs_iter
492
if r in weave_modifed_revisions or is_merging_rev(r)]
617
495
def get_view_revisions(mainline_revs, rev_nos, branch, direction,