~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Parth Malwankar
  • Date: 2010-05-03 08:33:32 UTC
  • mto: This revision was merged to the branch mainline in revision 5210.
  • Revision ID: parth.malwankar@gmail.com-20100503083332-233xyz4wwef6x3ey
removedĀ unusedĀ import.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
73
73
    repository as _mod_repository,
74
74
    revision as _mod_revision,
75
75
    revisionspec,
 
76
    trace,
76
77
    tsort,
77
 
    i18n,
78
78
    )
79
79
""")
80
80
 
81
81
from bzrlib import (
82
 
    lazy_regex,
83
82
    registry,
84
83
    )
85
84
from bzrlib.osutils import (
86
85
    format_date,
87
86
    format_date_with_offset_in_original_timezone,
88
 
    get_diff_header_encoding,
89
87
    get_terminal_encoding,
 
88
    re_compile_checked,
90
89
    terminal_width,
91
90
    )
92
91
from bzrlib.symbol_versioning import (
111
110
    revno = 1
112
111
    for revision_id in branch.revision_history():
113
112
        this_inv = branch.repository.get_inventory(revision_id)
114
 
        if this_inv.has_id(file_id):
 
113
        if file_id in this_inv:
115
114
            this_ie = this_inv[file_id]
116
115
            this_path = this_inv.id2path(file_id)
117
116
        else:
232
231
                          delta_type=None,
233
232
                          diff_type=None, _match_using_deltas=True,
234
233
                          exclude_common_ancestry=False,
235
 
                          signature=False,
236
234
                          ):
237
235
    """Convenience function for making a logging request dictionary.
238
236
 
262
260
      generate; 1 for just the mainline; 0 for all levels.
263
261
 
264
262
    :param generate_tags: If True, include tags for matched revisions.
265
 
`
 
263
 
266
264
    :param delta_type: Either 'full', 'partial' or None.
267
265
      'full' means generate the complete delta - adds/deletes/modifies/etc;
268
266
      'partial' means filter the delta using specific_fileids;
280
278
 
281
279
    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
282
280
      range operator or as a graph difference.
283
 
 
284
 
    :param signature: show digital signature information
285
281
    """
286
282
    return {
287
283
        'direction': direction,
295
291
        'delta_type': delta_type,
296
292
        'diff_type': diff_type,
297
293
        'exclude_common_ancestry': exclude_common_ancestry,
298
 
        'signature': signature,
299
294
        # Add 'private' attributes for features that may be deprecated
300
295
        '_match_using_deltas': _match_using_deltas,
301
296
    }
303
298
 
304
299
def _apply_log_request_defaults(rqst):
305
300
    """Apply default values to a request dictionary."""
306
 
    result = _DEFAULT_REQUEST_PARAMS.copy()
 
301
    result = _DEFAULT_REQUEST_PARAMS
307
302
    if rqst:
308
303
        result.update(rqst)
309
304
    return result
310
305
 
311
306
 
312
 
def format_signature_validity(rev_id, repo):
313
 
    """get the signature validity
314
 
    
315
 
    :param rev_id: revision id to validate
316
 
    :param repo: repository of revision
317
 
    :return: human readable string to print to log
318
 
    """
319
 
    from bzrlib import gpg
320
 
 
321
 
    gpg_strategy = gpg.GPGStrategy(None)
322
 
    result = repo.verify_revision(rev_id, gpg_strategy)
323
 
    if result[0] == gpg.SIGNATURE_VALID:
324
 
        return "valid signature from {0}".format(result[1])
325
 
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
326
 
        return "unknown key {0}".format(result[1])
327
 
    if result[0] == gpg.SIGNATURE_NOT_VALID:
328
 
        return "invalid signature!"
329
 
    if result[0] == gpg.SIGNATURE_NOT_SIGNED:
330
 
        return "no signature"
331
 
 
332
 
 
333
307
class LogGenerator(object):
334
308
    """A generator of log revisions."""
335
309
 
387
361
            rqst['delta_type'] = None
388
362
        if not getattr(lf, 'supports_diff', False):
389
363
            rqst['diff_type'] = None
390
 
        if not getattr(lf, 'supports_signatures', False):
391
 
            rqst['signature'] = False
392
364
 
393
365
        # Find and print the interesting revisions
394
366
        generator = self._generator_factory(self.branch, rqst)
428
400
        levels = rqst.get('levels')
429
401
        limit = rqst.get('limit')
430
402
        diff_type = rqst.get('diff_type')
431
 
        show_signature = rqst.get('signature')
432
403
        log_count = 0
433
404
        revision_iterator = self._create_log_revision_iterator()
434
405
        for revs in revision_iterator:
440
411
                    diff = None
441
412
                else:
442
413
                    diff = self._format_diff(rev, rev_id, diff_type)
443
 
                if show_signature:
444
 
                    signature = format_signature_validity(rev_id,
445
 
                                                self.branch.repository)
446
 
                else:
447
 
                    signature = None
448
414
                yield LogRevision(rev, revno, merge_depth, delta,
449
 
                    self.rev_tag_dict.get(rev_id), diff, signature)
 
415
                    self.rev_tag_dict.get(rev_id), diff)
450
416
                if limit:
451
417
                    log_count += 1
452
418
                    if log_count >= limit:
466
432
        else:
467
433
            specific_files = None
468
434
        s = StringIO()
469
 
        path_encoding = get_diff_header_encoding()
470
435
        diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
471
 
            new_label='', path_encoding=path_encoding)
 
436
            new_label='')
472
437
        return s.getvalue()
473
438
 
474
439
    def _create_log_revision_iterator(self):
557
522
    elif not generate_merge_revisions:
558
523
        # If we only want to see linear revisions, we can iterate ...
559
524
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
560
 
                                             direction, exclude_common_ancestry)
 
525
                                             direction)
561
526
        if direction == 'forward':
562
527
            iter_revs = reversed(iter_revs)
563
528
    else:
574
539
        # It's the tip
575
540
        return [(br_rev_id, br_revno, 0)]
576
541
    else:
577
 
        revno_str = _compute_revno_str(branch, rev_id)
 
542
        revno = branch.revision_id_to_dotted_revno(rev_id)
 
543
        revno_str = '.'.join(str(n) for n in revno)
578
544
        return [(rev_id, revno_str, 0)]
579
545
 
580
546
 
581
 
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
582
 
                             exclude_common_ancestry=False):
583
 
    result = _linear_view_revisions(
584
 
        branch, start_rev_id, end_rev_id,
585
 
        exclude_common_ancestry=exclude_common_ancestry)
 
547
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction):
 
548
    result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
586
549
    # If a start limit was given and it's not obviously an
587
550
    # ancestor of the end limit, check it before outputting anything
588
551
    if direction == 'forward' or (start_rev_id
609
572
    if delayed_graph_generation:
610
573
        try:
611
574
            for rev_id, revno, depth in  _linear_view_revisions(
612
 
                branch, start_rev_id, end_rev_id, exclude_common_ancestry):
 
575
                branch, start_rev_id, end_rev_id):
613
576
                if _has_merges(branch, rev_id):
614
577
                    # The end_rev_id can be nested down somewhere. We need an
615
578
                    # explicit ancestry check. There is an ambiguity here as we
660
623
    return len(parents) > 1
661
624
 
662
625
 
663
 
def _compute_revno_str(branch, rev_id):
664
 
    """Compute the revno string from a rev_id.
665
 
 
666
 
    :return: The revno string, or None if the revision is not in the supplied
667
 
        branch.
668
 
    """
669
 
    try:
670
 
        revno = branch.revision_id_to_dotted_revno(rev_id)
671
 
    except errors.NoSuchRevision:
672
 
        # The revision must be outside of this branch
673
 
        return None
674
 
    else:
675
 
        return '.'.join(str(n) for n in revno)
676
 
 
677
 
 
678
626
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
679
627
    """Is start_rev_id an obvious ancestor of end_rev_id?"""
680
628
    if start_rev_id and end_rev_id:
681
 
        try:
682
 
            start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
683
 
            end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
684
 
        except errors.NoSuchRevision:
685
 
            # one or both is not in the branch; not obvious
686
 
            return False
 
629
        start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
 
630
        end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
687
631
        if len(start_dotted) == 1 and len(end_dotted) == 1:
688
632
            # both on mainline
689
633
            return start_dotted[0] <= end_dotted[0]
699
643
    return True
700
644
 
701
645
 
702
 
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
703
 
                           exclude_common_ancestry=False):
 
646
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
704
647
    """Calculate a sequence of revisions to view, newest to oldest.
705
648
 
706
649
    :param start_rev_id: the lower revision-id
707
650
    :param end_rev_id: the upper revision-id
708
 
    :param exclude_common_ancestry: Whether the start_rev_id should be part of
709
 
        the iterated revisions.
710
651
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
711
652
    :raises _StartNotLinearAncestor: if a start_rev_id is specified but
712
 
        is not found walking the left-hand history
 
653
      is not found walking the left-hand history
713
654
    """
714
655
    br_revno, br_rev_id = branch.last_revision_info()
715
656
    repo = branch.repository
716
 
    graph = repo.get_graph()
717
657
    if start_rev_id is None and end_rev_id is None:
718
658
        cur_revno = br_revno
719
 
        for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
720
 
            (_mod_revision.NULL_REVISION,)):
 
659
        for revision_id in repo.iter_reverse_revision_history(br_rev_id):
721
660
            yield revision_id, str(cur_revno), 0
722
661
            cur_revno -= 1
723
662
    else:
724
663
        if end_rev_id is None:
725
664
            end_rev_id = br_rev_id
726
665
        found_start = start_rev_id is None
727
 
        for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
728
 
                (_mod_revision.NULL_REVISION,)):
729
 
            revno_str = _compute_revno_str(branch, revision_id)
 
666
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
 
667
            revno = branch.revision_id_to_dotted_revno(revision_id)
 
668
            revno_str = '.'.join(str(n) for n in revno)
730
669
            if not found_start and revision_id == start_rev_id:
731
 
                if not exclude_common_ancestry:
732
 
                    yield revision_id, revno_str, 0
 
670
                yield revision_id, revno_str, 0
733
671
                found_start = True
734
672
                break
735
673
            else:
864
802
    """
865
803
    if search is None:
866
804
        return log_rev_iterator
867
 
    searchRE = lazy_regex.lazy_compile(search, re.IGNORECASE)
 
805
    searchRE = re_compile_checked(search, re.IGNORECASE,
 
806
            'log message filter')
868
807
    return _filter_message_re(searchRE, log_rev_iterator)
869
808
 
870
809
 
1124
1063
    cur_revno = branch_revno
1125
1064
    rev_nos = {}
1126
1065
    mainline_revs = []
1127
 
    graph = branch.repository.get_graph()
1128
 
    for revision_id in graph.iter_lefthand_ancestry(
1129
 
            branch_last_revision, (_mod_revision.NULL_REVISION,)):
 
1066
    for revision_id in branch.repository.iter_reverse_revision_history(
 
1067
                        branch_last_revision):
1130
1068
        if cur_revno < start_revno:
1131
1069
            # We have gone far enough, but we always add 1 more revision
1132
1070
            rev_nos[revision_id] = cur_revno
1198
1136
    This includes the revisions which directly change the file id,
1199
1137
    and the revisions which merge these changes. So if the
1200
1138
    revision graph is::
1201
 
 
1202
1139
        A-.
1203
1140
        |\ \
1204
1141
        B C E
1231
1168
    """
1232
1169
    # Lookup all possible text keys to determine which ones actually modified
1233
1170
    # the file.
1234
 
    graph = branch.repository.get_file_graph()
1235
 
    get_parent_map = graph.get_parent_map
1236
1171
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1237
1172
    next_keys = None
1238
1173
    # Looking up keys in batches of 1000 can cut the time in half, as well as
1242
1177
    #       indexing layer. We might consider passing in hints as to the known
1243
1178
    #       access pattern (sparse/clustered, high success rate/low success
1244
1179
    #       rate). This particular access is clustered with a low success rate.
 
1180
    get_parent_map = branch.repository.texts.get_parent_map
1245
1181
    modified_text_revisions = set()
1246
1182
    chunk_size = 1000
1247
1183
    for start in xrange(0, len(text_keys), chunk_size):
1355
1291
    """
1356
1292
 
1357
1293
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1358
 
                 tags=None, diff=None, signature=None):
 
1294
                 tags=None, diff=None):
1359
1295
        self.rev = rev
1360
 
        if revno is None:
1361
 
            self.revno = None
1362
 
        else:
1363
 
            self.revno = str(revno)
 
1296
        self.revno = str(revno)
1364
1297
        self.merge_depth = merge_depth
1365
1298
        self.delta = delta
1366
1299
        self.tags = tags
1367
1300
        self.diff = diff
1368
 
        self.signature = signature
1369
1301
 
1370
1302
 
1371
1303
class LogFormatter(object):
1380
1312
    to indicate which LogRevision attributes it supports:
1381
1313
 
1382
1314
    - supports_delta must be True if this log formatter supports delta.
1383
 
      Otherwise the delta attribute may not be populated.  The 'delta_format'
1384
 
      attribute describes whether the 'short_status' format (1) or the long
1385
 
      one (2) should be used.
 
1315
        Otherwise the delta attribute may not be populated.  The 'delta_format'
 
1316
        attribute describes whether the 'short_status' format (1) or the long
 
1317
        one (2) should be used.
1386
1318
 
1387
1319
    - supports_merge_revisions must be True if this log formatter supports
1388
 
      merge revisions.  If not, then only mainline revisions will be passed
1389
 
      to the formatter.
 
1320
        merge revisions.  If not, then only mainline revisions will be passed
 
1321
        to the formatter.
1390
1322
 
1391
1323
    - preferred_levels is the number of levels this formatter defaults to.
1392
 
      The default value is zero meaning display all levels.
1393
 
      This value is only relevant if supports_merge_revisions is True.
 
1324
        The default value is zero meaning display all levels.
 
1325
        This value is only relevant if supports_merge_revisions is True.
1394
1326
 
1395
1327
    - supports_tags must be True if this log formatter supports tags.
1396
 
      Otherwise the tags attribute may not be populated.
 
1328
        Otherwise the tags attribute may not be populated.
1397
1329
 
1398
1330
    - supports_diff must be True if this log formatter supports diffs.
1399
 
      Otherwise the diff attribute may not be populated.
1400
 
 
1401
 
    - supports_signatures must be True if this log formatter supports GPG
1402
 
      signatures.
 
1331
        Otherwise the diff attribute may not be populated.
1403
1332
 
1404
1333
    Plugins can register functions to show custom revision properties using
1405
1334
    the properties_handler_registry. The registered function
1406
 
    must respect the following interface description::
1407
 
 
 
1335
    must respect the following interface description:
1408
1336
        def my_show_properties(properties_dict):
1409
1337
            # code that returns a dict {'name':'value'} of the properties
1410
1338
            # to be shown
1413
1341
 
1414
1342
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1415
1343
                 delta_format=None, levels=None, show_advice=False,
1416
 
                 to_exact_file=None, author_list_handler=None):
 
1344
                 to_exact_file=None):
1417
1345
        """Create a LogFormatter.
1418
1346
 
1419
1347
        :param to_file: the file to output to
1427
1355
          let the log formatter decide.
1428
1356
        :param show_advice: whether to show advice at the end of the
1429
1357
          log or not
1430
 
        :param author_list_handler: callable generating a list of
1431
 
          authors to display for a given revision
1432
1358
        """
1433
1359
        self.to_file = to_file
1434
1360
        # 'exact' stream used to show diff, it should print content 'as is'
1449
1375
        self.levels = levels
1450
1376
        self._show_advice = show_advice
1451
1377
        self._merge_count = 0
1452
 
        self._author_list_handler = author_list_handler
1453
1378
 
1454
1379
    def get_levels(self):
1455
1380
        """Get the number of levels to display or 0 for all."""
1487
1412
        return address
1488
1413
 
1489
1414
    def short_author(self, rev):
1490
 
        return self.authors(rev, 'first', short=True, sep=', ')
1491
 
 
1492
 
    def authors(self, rev, who, short=False, sep=None):
1493
 
        """Generate list of authors, taking --authors option into account.
1494
 
 
1495
 
        The caller has to specify the name of a author list handler,
1496
 
        as provided by the author list registry, using the ``who``
1497
 
        argument.  That name only sets a default, though: when the
1498
 
        user selected a different author list generation using the
1499
 
        ``--authors`` command line switch, as represented by the
1500
 
        ``author_list_handler`` constructor argument, that value takes
1501
 
        precedence.
1502
 
 
1503
 
        :param rev: The revision for which to generate the list of authors.
1504
 
        :param who: Name of the default handler.
1505
 
        :param short: Whether to shorten names to either name or address.
1506
 
        :param sep: What separator to use for automatic concatenation.
1507
 
        """
1508
 
        if self._author_list_handler is not None:
1509
 
            # The user did specify --authors, which overrides the default
1510
 
            author_list_handler = self._author_list_handler
1511
 
        else:
1512
 
            # The user didn't specify --authors, so we use the caller's default
1513
 
            author_list_handler = author_list_registry.get(who)
1514
 
        names = author_list_handler(rev)
1515
 
        if short:
1516
 
            for i in range(len(names)):
1517
 
                name, address = config.parse_username(names[i])
1518
 
                if name:
1519
 
                    names[i] = name
1520
 
                else:
1521
 
                    names[i] = address
1522
 
        if sep is not None:
1523
 
            names = sep.join(names)
1524
 
        return names
 
1415
        name, address = config.parse_username(rev.get_apparent_authors()[0])
 
1416
        if name:
 
1417
            return name
 
1418
        return address
1525
1419
 
1526
1420
    def merge_marker(self, revision):
1527
1421
        """Get the merge marker to include in the output or '' if none."""
1597
1491
    supports_delta = True
1598
1492
    supports_tags = True
1599
1493
    supports_diff = True
1600
 
    supports_signatures = True
1601
1494
 
1602
1495
    def __init__(self, *args, **kwargs):
1603
1496
        super(LongLogFormatter, self).__init__(*args, **kwargs)
1623
1516
                self.merge_marker(revision)))
1624
1517
        if revision.tags:
1625
1518
            lines.append('tags: %s' % (', '.join(revision.tags)))
1626
 
        if self.show_ids or revision.revno is None:
 
1519
        if self.show_ids:
1627
1520
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
1628
 
        if self.show_ids:
1629
1521
            for parent_id in revision.rev.parent_ids:
1630
1522
                lines.append('parent: %s' % (parent_id,))
1631
1523
        lines.extend(self.custom_properties(revision.rev))
1632
1524
 
1633
1525
        committer = revision.rev.committer
1634
 
        authors = self.authors(revision.rev, 'all')
 
1526
        authors = revision.rev.get_apparent_authors()
1635
1527
        if authors != [committer]:
1636
1528
            lines.append('author: %s' % (", ".join(authors),))
1637
1529
        lines.append('committer: %s' % (committer,))
1642
1534
 
1643
1535
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1644
1536
 
1645
 
        if revision.signature is not None:
1646
 
            lines.append('signature: ' + revision.signature)
1647
 
 
1648
1537
        lines.append('message:')
1649
1538
        if not revision.rev.message:
1650
1539
            lines.append('  (no message)')
1697
1586
        indent = '    ' * depth
1698
1587
        revno_width = self.revno_width_by_depth.get(depth)
1699
1588
        if revno_width is None:
1700
 
            if revision.revno is None or revision.revno.find('.') == -1:
 
1589
            if revision.revno.find('.') == -1:
1701
1590
                # mainline revno, e.g. 12345
1702
1591
                revno_width = 5
1703
1592
            else:
1711
1600
        if revision.tags:
1712
1601
            tags = ' {%s}' % (', '.join(revision.tags))
1713
1602
        to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1714
 
                revision.revno or "", self.short_author(revision.rev),
 
1603
                revision.revno, self.short_author(revision.rev),
1715
1604
                format_date(revision.rev.timestamp,
1716
1605
                            revision.rev.timezone or 0,
1717
1606
                            self.show_timezone, date_fmt="%Y-%m-%d",
1718
1607
                            show_offset=False),
1719
1608
                tags, self.merge_marker(revision)))
1720
1609
        self.show_properties(revision.rev, indent+offset)
1721
 
        if self.show_ids or revision.revno is None:
 
1610
        if self.show_ids:
1722
1611
            to_file.write(indent + offset + 'revision-id:%s\n'
1723
1612
                          % (revision.rev.revision_id,))
1724
1613
        if not revision.rev.message:
1777
1666
 
1778
1667
    def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1779
1668
        """Format log info into one string. Truncate tail of string
1780
 
 
1781
 
        :param revno:      revision number or None.
1782
 
                           Revision numbers counts from 1.
1783
 
        :param rev:        revision object
1784
 
        :param max_chars:  maximum length of resulting string
1785
 
        :param tags:       list of tags or None
1786
 
        :param prefix:     string to prefix each line
1787
 
        :return:           formatted truncated string
 
1669
        :param  revno:      revision number or None.
 
1670
                            Revision numbers counts from 1.
 
1671
        :param  rev:        revision object
 
1672
        :param  max_chars:  maximum length of resulting string
 
1673
        :param  tags:       list of tags or None
 
1674
        :param  prefix:     string to prefix each line
 
1675
        :return:            formatted truncated string
1788
1676
        """
1789
1677
        out = []
1790
1678
        if revno:
1791
1679
            # show revno only when is not None
1792
1680
            out.append("%s:" % revno)
1793
 
        if max_chars is not None:
1794
 
            out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1795
 
        else:
1796
 
            out.append(self.short_author(rev))
 
1681
        out.append(self.truncate(self.short_author(rev), 20))
1797
1682
        out.append(self.date_string(rev))
1798
1683
        if len(rev.parent_ids) > 1:
1799
1684
            out.append('[merge]')
1818
1703
                               self.show_timezone,
1819
1704
                               date_fmt='%Y-%m-%d',
1820
1705
                               show_offset=False)
1821
 
        committer_str = self.authors(revision.rev, 'first', sep=', ')
1822
 
        committer_str = committer_str.replace(' <', '  <')
 
1706
        committer_str = revision.rev.get_apparent_authors()[0].replace (' <', '  <')
1823
1707
        to_file.write('%s  %s\n\n' % (date_str,committer_str))
1824
1708
 
1825
1709
        if revision.delta is not None and revision.delta.has_changed():
1890
1774
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1891
1775
 
1892
1776
 
1893
 
def author_list_all(rev):
1894
 
    return rev.get_apparent_authors()[:]
1895
 
 
1896
 
 
1897
 
def author_list_first(rev):
1898
 
    lst = rev.get_apparent_authors()
1899
 
    try:
1900
 
        return [lst[0]]
1901
 
    except IndexError:
1902
 
        return []
1903
 
 
1904
 
 
1905
 
def author_list_committer(rev):
1906
 
    return [rev.committer]
1907
 
 
1908
 
 
1909
 
author_list_registry = registry.Registry()
1910
 
 
1911
 
author_list_registry.register('all', author_list_all,
1912
 
                              'All authors')
1913
 
 
1914
 
author_list_registry.register('first', author_list_first,
1915
 
                              'The first author')
1916
 
 
1917
 
author_list_registry.register('committer', author_list_committer,
1918
 
                              'The committer')
1919
 
 
1920
 
 
1921
1777
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1922
1778
    # deprecated; for compatibility
1923
1779
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1992
1848
    old_revisions = set()
1993
1849
    new_history = []
1994
1850
    new_revisions = set()
1995
 
    graph = repository.get_graph()
1996
 
    new_iter = graph.iter_lefthand_ancestry(new_revision_id)
1997
 
    old_iter = graph.iter_lefthand_ancestry(old_revision_id)
 
1851
    new_iter = repository.iter_reverse_revision_history(new_revision_id)
 
1852
    old_iter = repository.iter_reverse_revision_history(old_revision_id)
1998
1853
    stop_revision = None
1999
1854
    do_old = True
2000
1855
    do_new = True
2075
1930
        lf.log_revision(lr)
2076
1931
 
2077
1932
 
2078
 
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
 
1933
def _get_info_for_log_files(revisionspec_list, file_list):
2079
1934
    """Find file-ids and kinds given a list of files and a revision range.
2080
1935
 
2081
1936
    We search for files at the end of the range. If not found there,
2085
1940
    :param file_list: the list of paths given on the command line;
2086
1941
      the first of these can be a branch location or a file path,
2087
1942
      the remainder must be file paths
2088
 
    :param add_cleanup: When the branch returned is read locked,
2089
 
      an unlock call will be queued to the cleanup.
2090
1943
    :return: (branch, info_list, start_rev_info, end_rev_info) where
2091
1944
      info_list is a list of (relative_path, file_id, kind) tuples where
2092
1945
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2093
1946
      branch will be read-locked.
2094
1947
    """
2095
 
    from builtins import _get_revision_range
 
1948
    from builtins import _get_revision_range, safe_relpath_files
2096
1949
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2097
 
    add_cleanup(b.lock_read().unlock)
 
1950
    b.lock_read()
2098
1951
    # XXX: It's damn messy converting a list of paths to relative paths when
2099
1952
    # those paths might be deleted ones, they might be on a case-insensitive
2100
1953
    # filesystem and/or they might be in silly locations (like another branch).
2104
1957
    # case of running log in a nested directory, assuming paths beyond the
2105
1958
    # first one haven't been deleted ...
2106
1959
    if tree:
2107
 
        relpaths = [path] + tree.safe_relpath_files(file_list[1:])
 
1960
        relpaths = [path] + safe_relpath_files(tree, file_list[1:])
2108
1961
    else:
2109
1962
        relpaths = [path] + file_list[1:]
2110
1963
    info_list = []