~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-02-02 09:14:14 UTC
  • mfrom: (3976.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090202091414-4q20mjzsvp03vyfc
Faster log (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
52
52
import codecs
53
53
from cStringIO import StringIO
54
54
from itertools import (
 
55
    chain,
55
56
    izip,
56
57
    )
57
58
import re
199
200
    """Worker function for show_log - see show_log."""
200
201
    if not isinstance(lf, LogFormatter):
201
202
        warn("not a LogFormatter instance: %r" % lf)
202
 
 
203
203
    if specific_fileid:
204
204
        trace.mutter('get log for file_id %r', specific_fileid)
 
205
 
 
206
    # Consult the LogFormatter about what it needs and can handle
205
207
    levels_to_display = lf.get_levels()
206
208
    generate_merge_revisions = levels_to_display != 1
207
209
    allow_single_merge_revision = True
208
210
    if not getattr(lf, 'supports_merge_revisions', False):
209
211
        allow_single_merge_revision = getattr(lf,
210
212
            'supports_single_merge_revision', False)
211
 
    view_revisions = calculate_view_revisions(branch, start_revision,
212
 
                                              end_revision, direction,
213
 
                                              specific_fileid,
214
 
                                              generate_merge_revisions,
215
 
                                              allow_single_merge_revision)
216
 
    rev_tag_dict = {}
217
213
    generate_tags = getattr(lf, 'supports_tags', False)
218
 
    if generate_tags:
219
 
        if branch.supports_tags():
220
 
            rev_tag_dict = branch.tags.get_reverse_tag_dict()
221
 
 
 
214
    if generate_tags and branch.supports_tags():
 
215
        rev_tag_dict = branch.tags.get_reverse_tag_dict()
 
216
    else:
 
217
        rev_tag_dict = {}
222
218
    generate_delta = verbose and getattr(lf, 'supports_delta', False)
223
219
    generate_diff = show_diff and getattr(lf, 'supports_diff', False)
224
220
 
225
 
    # now we just print all the revisions
 
221
    # Find and print the interesting revisions
226
222
    repo = branch.repository
227
223
    log_count = 0
228
 
    revision_iterator = make_log_rev_iterator(branch, view_revisions,
229
 
        generate_delta, search)
 
224
    revision_iterator = _create_log_revision_iterator(branch,
 
225
        start_revision, end_revision, direction, specific_fileid, search,
 
226
        generate_merge_revisions, allow_single_merge_revision,
 
227
        generate_delta, limited_output=limit > 0)
230
228
    for revs in revision_iterator:
231
229
        for (rev_id, revno, merge_depth), rev, delta in revs:
232
230
            # Note: 0 levels means show everything; merge_depth counts from 0
262
260
    return s.getvalue()
263
261
 
264
262
 
265
 
def calculate_view_revisions(branch, start_revision, end_revision, direction,
266
 
                             specific_fileid, generate_merge_revisions,
267
 
                             allow_single_merge_revision):
268
 
    if (    not generate_merge_revisions
269
 
        and start_revision is end_revision is None
270
 
        and direction == 'reverse'
271
 
        and specific_fileid is None):
272
 
        return _linear_view_revisions(branch)
273
 
 
274
 
    mainline_revs, rev_nos, start_rev_id, end_rev_id = _get_mainline_revs(
275
 
        branch, start_revision, end_revision)
276
 
    if not mainline_revs:
 
263
class _StartNotLinearAncestor(Exception):
 
264
    """Raised when a start revision is not found walking left-hand history."""
 
265
 
 
266
 
 
267
def _create_log_revision_iterator(branch, start_revision, end_revision,
 
268
    direction, specific_fileid, search, generate_merge_revisions,
 
269
    allow_single_merge_revision, generate_delta, limited_output=False):
 
270
    """Create a revision iterator for log.
 
271
 
 
272
    :param branch: The branch being logged.
 
273
    :param start_revision: If not None, only show revisions >= start_revision
 
274
    :param end_revision: If not None, only show revisions <= end_revision
 
275
    :param direction: 'reverse' (default) is latest to earliest; 'forward' is
 
276
        earliest to latest.
 
277
    :param specific_fileid: If not None, list only the commits affecting the
 
278
        specified file.
 
279
    :param search: If not None, only show revisions with matching commit
 
280
        messages.
 
281
    :param generate_merge_revisions: If False, show only mainline revisions.
 
282
    :param allow_single_merge_revision: If True, logging of a single
 
283
        revision off the mainline is to be allowed
 
284
    :param generate_delta: Whether to generate a delta for each revision.
 
285
    :param limited_output: if True, the user only wants a limited result
 
286
 
 
287
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
 
288
        delta).
 
289
    """
 
290
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
 
291
        end_revision)
 
292
 
 
293
    # Decide how file-ids are matched: delta-filtering vs per-file graph.
 
294
    # Delta filtering allows revisions to be displayed incrementally
 
295
    # though the total time is much slower for huge repositories: log -v
 
296
    # is the *lower* performance bound. At least until the split
 
297
    # inventory format arrives, per-file-graph needs to remain the
 
298
    # default except in verbose mode. Delta filtering should give more
 
299
    # accurate results (e.g. inclusion of FILE deletions) so arguably
 
300
    # it should always be used in the future.
 
301
    use_deltas_for_matching = specific_fileid and generate_delta
 
302
    delayed_graph_generation = not specific_fileid and (
 
303
            start_rev_id or end_rev_id or limited_output)
 
304
    generate_merges = generate_merge_revisions or (specific_fileid and
 
305
        not use_deltas_for_matching)
 
306
    view_revisions = _calc_view_revisions(branch, start_rev_id, end_rev_id,
 
307
        direction, generate_merges, allow_single_merge_revision,
 
308
        delayed_graph_generation=delayed_graph_generation)
 
309
    search_deltas_for_fileids = None
 
310
    if use_deltas_for_matching:
 
311
        search_deltas_for_fileids = set([specific_fileid])
 
312
    elif specific_fileid:
 
313
        if not isinstance(view_revisions, list):
 
314
            view_revisions = list(view_revisions)
 
315
        view_revisions = _filter_revisions_touching_file_id(branch,
 
316
            specific_fileid, view_revisions,
 
317
            include_merges=generate_merge_revisions)
 
318
    return make_log_rev_iterator(branch, view_revisions, generate_delta,
 
319
        search, file_ids=search_deltas_for_fileids, direction=direction)
 
320
 
 
321
 
 
322
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
 
323
    generate_merge_revisions, allow_single_merge_revision,
 
324
    delayed_graph_generation=False):
 
325
    """Calculate the revisions to view.
 
326
 
 
327
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
 
328
             a list of the same tuples.
 
329
    """
 
330
    br_revno, br_rev_id = branch.last_revision_info()
 
331
    if br_revno == 0:
277
332
        return []
278
333
 
279
 
    generate_single_revision = False
280
 
    if ((not generate_merge_revisions)
281
 
        and ((start_rev_id and (start_rev_id not in rev_nos))
282
 
            or (end_rev_id and (end_rev_id not in rev_nos)))):
283
 
        generate_single_revision = ((start_rev_id == end_rev_id)
284
 
            and allow_single_merge_revision)
285
 
        if not generate_single_revision:
286
 
            raise errors.BzrCommandError('Selected log formatter only supports'
287
 
                ' mainline revisions.')
288
 
        generate_merge_revisions = generate_single_revision
289
 
    include_merges = generate_merge_revisions or specific_fileid
290
 
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
291
 
                          direction, include_merges=include_merges)
292
 
 
 
334
    # If a single revision is requested, check we can handle it
 
335
    generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
 
336
        (not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
 
337
    if generate_single_revision:
 
338
        if end_rev_id == br_rev_id:
 
339
            # It's the tip
 
340
            return [(br_rev_id, br_revno, 0)]
 
341
        else:
 
342
            revno = branch.revision_id_to_dotted_revno(end_rev_id)
 
343
            if len(revno) > 1 and not allow_single_merge_revision:
 
344
                # It's a merge revision and the log formatter is
 
345
                # completely brain dead. This "feature" of allowing
 
346
                # log formatters incapable of displaying dotted revnos
 
347
                # ought to be deprecated IMNSHO. IGC 20091022
 
348
                raise errors.BzrCommandError('Selected log formatter only'
 
349
                    ' supports mainline revisions.')
 
350
            revno_str = '.'.join(str(n) for n in revno)
 
351
            return [(end_rev_id, revno_str, 0)]
 
352
 
 
353
    # If we only want to see linear revisions, we can iterate ...
 
354
    if not generate_merge_revisions:
 
355
        result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
 
356
        # If a start limit was given and it's not obviously an
 
357
        # ancestor of the end limit, check it before outputting anything
 
358
        if direction == 'forward' or (start_rev_id
 
359
            and not _is_obvious_ancestor(branch, start_rev_id, end_rev_id)):
 
360
            try:
 
361
                result = list(result)
 
362
            except _StartNotLinearAncestor:
 
363
                raise errors.BzrCommandError('Start revision not found in'
 
364
                    ' left-hand history of end revision.')
 
365
        if direction == 'forward':
 
366
            result = reversed(list(result))
 
367
        return result
 
368
 
 
369
    # On large trees, generating the merge graph can take 30-60 seconds
 
370
    # so we delay doing it until a merge is detected, incrementally
 
371
    # returning initial (non-merge) revisions while we can.
 
372
    initial_revisions = []
 
373
    if delayed_graph_generation:
 
374
        try:
 
375
            for rev_id, revno, depth in \
 
376
                _linear_view_revisions(branch, start_rev_id, end_rev_id):
 
377
                if _has_merges(branch, rev_id):
 
378
                    end_rev_id = rev_id
 
379
                    break
 
380
                else:
 
381
                    initial_revisions.append((rev_id, revno, depth))
 
382
            else:
 
383
                # No merged revisions found
 
384
                if direction == 'reverse':
 
385
                    return initial_revisions
 
386
                elif direction == 'forward':
 
387
                    return reversed(initial_revisions)
 
388
                else:
 
389
                    raise ValueError('invalid direction %r' % direction)
 
390
        except _StartNotLinearAncestor:
 
391
            # A merge was never detected so the lower revision limit can't
 
392
            # be nested down somewhere
 
393
            raise errors.BzrCommandError('Start revision not found in'
 
394
                ' history of end revision.')
 
395
 
 
396
    # A log including nested merges is required. If the direction is reverse,
 
397
    # we rebase the initial merge depths so that the development line is
 
398
    # shown naturally, i.e. just like it is for linear logging. We can easily
 
399
    # make forward the exact opposite display, but showing the merge revisions
 
400
    # indented at the end seems slightly nicer in that case.
 
401
    view_revisions = chain(iter(initial_revisions),
 
402
        _graph_view_revisions(branch, start_rev_id, end_rev_id,
 
403
        rebase_initial_depths=direction == 'reverse'))
293
404
    if direction == 'reverse':
294
 
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
295
 
    view_revisions = _filter_revision_range(list(view_revs_iter),
296
 
                                            start_rev_id,
297
 
                                            end_rev_id)
298
 
    if view_revisions and generate_single_revision:
299
 
        view_revisions = view_revisions[0:1]
 
405
        return view_revisions
 
406
    elif direction == 'forward':
 
407
        # Forward means oldest first, adjusting for depth.
 
408
        view_revisions = reverse_by_depth(list(view_revisions))
 
409
        return _rebase_merge_depth(view_revisions)
 
410
    else:
 
411
        raise ValueError('invalid direction %r' % direction)
 
412
 
 
413
 
 
414
def _has_merges(branch, rev_id):
 
415
    """Does a revision have multiple parents or not?"""
 
416
    parents = branch.repository.get_parent_map([rev_id]).get(rev_id, [])
 
417
    return len(parents) > 1
 
418
 
 
419
 
 
420
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
 
421
    """Is start_rev_id an obvious ancestor of end_rev_id?"""
 
422
    if start_rev_id and end_rev_id:
 
423
        start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
 
424
        end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
 
425
        if len(start_dotted) == 1 and len(end_dotted) == 1:
 
426
            # both on mainline
 
427
            return start_dotted[0] <= end_dotted[0]
 
428
        elif (len(start_dotted) == 3 and len(end_dotted) == 3 and
 
429
            start_dotted[0:1] == end_dotted[0:1]):
 
430
            # both on same development line
 
431
            return start_dotted[2] <= end_dotted[2]
 
432
        else:
 
433
            # not obvious
 
434
            return False
 
435
    return True
 
436
 
 
437
 
 
438
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
 
439
    """Calculate a sequence of revisions to view, newest to oldest.
 
440
 
 
441
    :param start_rev_id: the lower revision-id
 
442
    :param end_rev_id: the upper revision-id
 
443
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
 
444
    :raises _StartNotLinearAncestor: if a start_rev_id is specified but
 
445
      is not found walking the left-hand history
 
446
    """
 
447
    br_revno, br_rev_id = branch.last_revision_info()
 
448
    repo = branch.repository
 
449
    if start_rev_id is None and end_rev_id is None:
 
450
        cur_revno = br_revno
 
451
        for revision_id in repo.iter_reverse_revision_history(br_rev_id):
 
452
            yield revision_id, str(cur_revno), 0
 
453
            cur_revno -= 1
 
454
    else:
 
455
        if end_rev_id is None:
 
456
            end_rev_id = br_rev_id
 
457
        found_start = start_rev_id is None
 
458
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
 
459
            revno = branch.revision_id_to_dotted_revno(revision_id)
 
460
            revno_str = '.'.join(str(n) for n in revno)
 
461
            if not found_start and revision_id == start_rev_id:
 
462
                yield revision_id, revno_str, 0
 
463
                found_start = True
 
464
                break
 
465
            else:
 
466
                yield revision_id, revno_str, 0
 
467
        else:
 
468
            if not found_start:
 
469
                raise _StartNotLinearAncestor()
 
470
 
 
471
 
 
472
def _graph_view_revisions(branch, start_rev_id, end_rev_id,
 
473
    rebase_initial_depths=True):
 
474
    """Calculate revisions to view including merges, newest to oldest.
 
475
 
 
476
    :param branch: the branch
 
477
    :param start_rev_id: the lower revision-id
 
478
    :param end_rev_id: the upper revision-id
 
479
    :param rebase_initial_depth: should depths be rebased until a mainline
 
480
      revision is found?
 
481
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
 
482
    """
 
483
    view_revisions = branch.iter_merge_sorted_revisions(
 
484
        start_revision_id=end_rev_id, stop_revision_id=start_rev_id,
 
485
        stop_rule="with-merges")
 
486
    if not rebase_initial_depths:
 
487
        for (rev_id, merge_depth, revno, end_of_merge
 
488
             ) in view_revisions:
 
489
            yield rev_id, '.'.join(map(str, revno)), merge_depth
 
490
    else:
 
491
        # We're following a development line starting at a merged revision.
 
492
        # We need to adjust depths down by the initial depth until we find
 
493
        # a depth less than it. Then we use that depth as the adjustment.
 
494
        # If and when we reach the mainline, depth adjustment ends.
 
495
        depth_adjustment = None
 
496
        for (rev_id, merge_depth, revno, end_of_merge
 
497
             ) in view_revisions:
 
498
            if depth_adjustment is None:
 
499
                depth_adjustment = merge_depth
 
500
            if depth_adjustment:
 
501
                if merge_depth < depth_adjustment:
 
502
                    depth_adjustment = merge_depth
 
503
                merge_depth -= depth_adjustment
 
504
            yield rev_id, '.'.join(map(str, revno)), merge_depth
 
505
 
 
506
 
 
507
def calculate_view_revisions(branch, start_revision, end_revision, direction,
 
508
        specific_fileid, generate_merge_revisions, allow_single_merge_revision):
 
509
    """Calculate the revisions to view.
 
510
 
 
511
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
 
512
             a list of the same tuples.
 
513
    """
 
514
    # This method is no longer called by the main code path.
 
515
    # It is retained for API compatibility and may be deprecated
 
516
    # soon. IGC 20090116
 
517
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
 
518
        end_revision)
 
519
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
 
520
        direction, generate_merge_revisions or specific_fileid,
 
521
        allow_single_merge_revision))
300
522
    if specific_fileid:
301
523
        view_revisions = _filter_revisions_touching_file_id(branch,
302
524
            specific_fileid, view_revisions,
303
525
            include_merges=generate_merge_revisions)
304
 
 
305
 
    # rebase merge_depth - unless there are no revisions or 
306
 
    # either the first or last revision have merge_depth = 0.
 
526
    return _rebase_merge_depth(view_revisions)
 
527
 
 
528
 
 
529
def _rebase_merge_depth(view_revisions):
 
530
    """Adjust depths upwards so the top level is 0."""
 
531
    # If either the first or last revision have a merge_depth of 0, we're done
307
532
    if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
308
533
        min_depth = min([d for r,n,d in view_revisions])
309
534
        if min_depth != 0:
311
536
    return view_revisions
312
537
 
313
538
 
314
 
def _linear_view_revisions(branch):
315
 
    start_revno, start_revision_id = branch.last_revision_info()
316
 
    repo = branch.repository
317
 
    revision_ids = repo.iter_reverse_revision_history(start_revision_id)
318
 
    for num, revision_id in enumerate(revision_ids):
319
 
        yield revision_id, str(start_revno - num), 0
320
 
 
321
 
 
322
 
def make_log_rev_iterator(branch, view_revisions, generate_delta, search):
 
539
def make_log_rev_iterator(branch, view_revisions, generate_delta, search,
 
540
        file_ids=None, direction='reverse'):
323
541
    """Create a revision iterator for log.
324
542
 
325
543
    :param branch: The branch being logged.
326
544
    :param view_revisions: The revisions being viewed.
327
545
    :param generate_delta: Whether to generate a delta for each revision.
328
546
    :param search: A user text search string.
 
547
    :param file_ids: If non empty, only revisions matching one or more of
 
548
      the file-ids are to be kept.
 
549
    :param direction: the direction in which view_revisions is sorted
329
550
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
330
551
        delta).
331
552
    """
342
563
                yield (view, None, None)
343
564
        log_rev_iterator = iter([_convert()])
344
565
    for adapter in log_adapters:
345
 
        log_rev_iterator = adapter(branch, generate_delta, search,
346
 
            log_rev_iterator)
 
566
        # It would be nicer if log adapters were first class objects
 
567
        # with custom parameters. This will do for now. IGC 20090127
 
568
        if adapter == _make_delta_filter:
 
569
            log_rev_iterator = adapter(branch, generate_delta,
 
570
                search, log_rev_iterator, file_ids, direction)
 
571
        else:
 
572
            log_rev_iterator = adapter(branch, generate_delta,
 
573
                search, log_rev_iterator)
347
574
    return log_rev_iterator
348
575
 
349
576
 
374
601
        yield new_revs
375
602
 
376
603
 
377
 
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator):
 
604
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
 
605
    fileids=None, direction='reverse'):
378
606
    """Add revision deltas to a log iterator if needed.
379
607
 
380
608
    :param branch: The branch being logged.
382
610
    :param search: A user text search string.
383
611
    :param log_rev_iterator: An input iterator containing all revisions that
384
612
        could be displayed, in lists.
 
613
    :param fileids: If non empty, only revisions matching one or more of
 
614
      the file-ids are to be kept.
 
615
    :param direction: the direction in which view_revisions is sorted
385
616
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
386
617
        delta).
387
618
    """
388
 
    if not generate_delta:
 
619
    if not generate_delta and not fileids:
389
620
        return log_rev_iterator
390
 
    return _generate_deltas(branch.repository, log_rev_iterator)
391
 
 
392
 
 
393
 
def _generate_deltas(repository, log_rev_iterator):
394
 
    """Create deltas for each batch of revisions in log_rev_iterator."""
 
621
    return _generate_deltas(branch.repository, log_rev_iterator,
 
622
        generate_delta, fileids, direction)
 
623
 
 
624
 
 
625
def _generate_deltas(repository, log_rev_iterator, always_delta, fileids,
 
626
    direction):
 
627
    """Create deltas for each batch of revisions in log_rev_iterator.
 
628
    
 
629
    If we're only generating deltas for the sake of filtering against
 
630
    file-ids, we stop generating deltas once all file-ids reach the
 
631
    appropriate life-cycle point. If we're receiving data newest to
 
632
    oldest, then that life-cycle point is 'add', otherwise it's 'remove'.
 
633
    """
 
634
    check_fileids = fileids is not None and len(fileids) > 0
 
635
    if check_fileids:
 
636
        fileid_set = set(fileids)
 
637
        if direction == 'reverse':
 
638
            stop_on = 'add'
 
639
        else:
 
640
            stop_on = 'remove'
 
641
    else:
 
642
        fileid_set = None
395
643
    for revs in log_rev_iterator:
 
644
        # If we were matching against fileids and we've run out,
 
645
        # there's nothing left to do
 
646
        if check_fileids and not fileid_set:
 
647
            return
396
648
        revisions = [rev[1] for rev in revs]
397
649
        deltas = repository.get_deltas_for_revisions(revisions)
398
 
        revs = [(rev[0], rev[1], delta) for rev, delta in izip(revs, deltas)]
399
 
        yield revs
 
650
        new_revs = []
 
651
        for rev, delta in izip(revs, deltas):
 
652
            if check_fileids:
 
653
                if not _delta_matches_fileids(delta, fileid_set, stop_on):
 
654
                    continue
 
655
                elif not always_delta:
 
656
                    # Delta was created just for matching - ditch it
 
657
                    # Note: It would probably be a better UI to return
 
658
                    # a delta filtered by the file-ids, rather than
 
659
                    # None at all. That functional enhancement can
 
660
                    # come later ...
 
661
                    delta = None
 
662
            new_revs.append((rev[0], rev[1], delta))
 
663
        yield new_revs
 
664
 
 
665
 
 
666
def _delta_matches_fileids(delta, fileids, stop_on='add'):
 
667
    """Check is a delta matches one of more file-ids.
 
668
    
 
669
    :param fileids: a set of fileids to match against.
 
670
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
 
671
      fileids set once their add or remove entry is detected respectively
 
672
    """
 
673
    if not fileids:
 
674
        return False
 
675
    result = False
 
676
    for item in delta.added:
 
677
        if item[1] in fileids:
 
678
            if stop_on == 'add':
 
679
                fileids.remove(item[1])
 
680
            result = True
 
681
    for item in delta.removed:
 
682
        if item[1] in fileids:
 
683
            if stop_on == 'delete':
 
684
                fileids.remove(item[1])
 
685
            result = True
 
686
    if result:
 
687
        return True
 
688
    for l in (delta.modified, delta.renamed, delta.kind_changed):
 
689
        for item in l:
 
690
            if item[1] in fileids:
 
691
                return True
 
692
    return False
400
693
 
401
694
 
402
695
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
443
736
            num = min(int(num * 1.5), 200)
444
737
 
445
738
 
 
739
def _get_revision_limits(branch, start_revision, end_revision):
 
740
    """Get and check revision limits.
 
741
 
 
742
    :param  branch: The branch containing the revisions. 
 
743
 
 
744
    :param  start_revision: The first revision to be logged.
 
745
            For backwards compatibility this may be a mainline integer revno,
 
746
            but for merge revision support a RevisionInfo is expected.
 
747
 
 
748
    :param  end_revision: The last revision to be logged.
 
749
            For backwards compatibility this may be a mainline integer revno,
 
750
            but for merge revision support a RevisionInfo is expected.
 
751
 
 
752
    :return: (start_rev_id, end_rev_id) tuple.
 
753
    """
 
754
    branch_revno, branch_rev_id = branch.last_revision_info()
 
755
    start_rev_id = None
 
756
    if start_revision is None:
 
757
        start_revno = 1
 
758
    else:
 
759
        if isinstance(start_revision, revisionspec.RevisionInfo):
 
760
            start_rev_id = start_revision.rev_id
 
761
            start_revno = start_revision.revno or 1
 
762
        else:
 
763
            branch.check_real_revno(start_revision)
 
764
            start_revno = start_revision
 
765
            start_rev_id = branch.get_rev_id(start_revno)
 
766
 
 
767
    end_rev_id = None
 
768
    if end_revision is None:
 
769
        end_revno = branch_revno
 
770
    else:
 
771
        if isinstance(end_revision, revisionspec.RevisionInfo):
 
772
            end_rev_id = end_revision.rev_id
 
773
            end_revno = end_revision.revno or branch_revno
 
774
        else:
 
775
            branch.check_real_revno(end_revision)
 
776
            end_revno = end_revision
 
777
            end_rev_id = branch.get_rev_id(end_revno)
 
778
 
 
779
    if branch_revno != 0:
 
780
        if (start_rev_id == _mod_revision.NULL_REVISION
 
781
            or end_rev_id == _mod_revision.NULL_REVISION):
 
782
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
783
        if start_revno > end_revno:
 
784
            raise errors.BzrCommandError("Start revision must be older than "
 
785
                                         "the end revision.")
 
786
    return (start_rev_id, end_rev_id)
 
787
 
 
788
 
446
789
def _get_mainline_revs(branch, start_revision, end_revision):
447
790
    """Get the mainline revisions from the branch.
448
791
    
539
882
 
540
883
    :return: The filtered view_revisions.
541
884
    """
 
885
    # This method is no longer called by the main code path.
 
886
    # It may be removed soon. IGC 20090127
542
887
    if start_rev_id or end_rev_id:
543
888
        revision_ids = [r for r, n, d in view_revisions]
544
889
        if start_rev_id:
655
1000
    :return: an iterator of (revision_id, revno, merge_depth)
656
1001
    (if there is no revno for a revision, None is supplied)
657
1002
    """
 
1003
    # This method is no longer called by the main code path.
 
1004
    # It is retained for API compatibility and may be deprecated
 
1005
    # soon. IGC 20090127
658
1006
    if not include_merges:
659
1007
        revision_ids = mainline_revs[1:]
660
1008
        if direction == 'reverse':
731
1079
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
732
1080
                 tags=None, diff=None):
733
1081
        self.rev = rev
734
 
        self.revno = revno
 
1082
        self.revno = str(revno)
735
1083
        self.merge_depth = merge_depth
736
1084
        self.delta = delta
737
1085
        self.tags = tags
752
1100
    - supports_delta must be True if this log formatter supports delta.
753
1101
        Otherwise the delta attribute may not be populated.  The 'delta_format'
754
1102
        attribute describes whether the 'short_status' format (1) or the long
755
 
        one (2) sould be used.
 
1103
        one (2) should be used.
756
1104
 
757
1105
    - supports_merge_revisions must be True if this log formatter supports 
758
 
        merge revisions.  If not, and if supports_single_merge_revisions is
 
1106
        merge revisions.  If not, and if supports_single_merge_revision is
759
1107
        also not True, then only mainline revisions will be passed to the 
760
1108
        formatter.
761
1109