/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: John Arbash Meinel
  • Date: 2009-12-02 23:09:40 UTC
  • mfrom: (4853.1.1 whitespace)
  • mto: This revision was merged to the branch mainline in revision 4856.
  • Revision ID: john@arbash-meinel.com-20091202230940-7n2aydoxngdqxzld
Strip trailing whitespace from doc files by Patrick Regan.

Resolve one small conflict with another doc edit.
Also, revert the changes to all the .pdf and .png files. We shouldn't
be touching them as they are pure-binary files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2009 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
88
88
    re_compile_checked,
89
89
    terminal_width,
90
90
    )
91
 
from bzrlib.symbol_versioning import (
92
 
    deprecated_function,
93
 
    deprecated_in,
94
 
    )
95
91
 
96
92
 
97
93
def find_touching_revisions(branch, file_id):
109
105
    last_path = None
110
106
    revno = 1
111
107
    for revision_id in branch.revision_history():
112
 
        this_inv = branch.repository.get_inventory(revision_id)
 
108
        this_inv = branch.repository.get_revision_inventory(revision_id)
113
109
        if file_id in this_inv:
114
110
            this_ie = this_inv[file_id]
115
111
            this_path = this_inv.id2path(file_id)
220
216
    'direction': 'reverse',
221
217
    'levels': 1,
222
218
    'generate_tags': True,
223
 
    'exclude_common_ancestry': False,
224
219
    '_match_using_deltas': True,
225
220
    }
226
221
 
227
222
 
228
223
def make_log_request_dict(direction='reverse', specific_fileids=None,
229
 
                          start_revision=None, end_revision=None, limit=None,
230
 
                          message_search=None, levels=1, generate_tags=True,
231
 
                          delta_type=None,
232
 
                          diff_type=None, _match_using_deltas=True,
233
 
                          exclude_common_ancestry=False,
234
 
                          ):
 
224
    start_revision=None, end_revision=None, limit=None,
 
225
    message_search=None, levels=1, generate_tags=True, delta_type=None,
 
226
    diff_type=None, _match_using_deltas=True):
235
227
    """Convenience function for making a logging request dictionary.
236
228
 
237
229
    Using this function may make code slightly safer by ensuring
275
267
      algorithm used for matching specific_fileids. This parameter
276
268
      may be removed in the future so bzrlib client code should NOT
277
269
      use it.
278
 
 
279
 
    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
280
 
      range operator or as a graph difference.
281
270
    """
282
271
    return {
283
272
        'direction': direction,
290
279
        'generate_tags': generate_tags,
291
280
        'delta_type': delta_type,
292
281
        'diff_type': diff_type,
293
 
        'exclude_common_ancestry': exclude_common_ancestry,
294
282
        # Add 'private' attributes for features that may be deprecated
295
283
        '_match_using_deltas': _match_using_deltas,
296
284
    }
316
304
 
317
305
 
318
306
class Logger(object):
319
 
    """An object that generates, formats and displays a log."""
 
307
    """An object the generates, formats and displays a log."""
320
308
 
321
309
    def __init__(self, branch, rqst):
322
310
        """Create a Logger.
463
451
        generate_merge_revisions = rqst.get('levels') != 1
464
452
        delayed_graph_generation = not rqst.get('specific_fileids') and (
465
453
                rqst.get('limit') or self.start_rev_id or self.end_rev_id)
466
 
        view_revisions = _calc_view_revisions(
467
 
            self.branch, self.start_rev_id, self.end_rev_id,
468
 
            rqst.get('direction'),
469
 
            generate_merge_revisions=generate_merge_revisions,
470
 
            delayed_graph_generation=delayed_graph_generation,
471
 
            exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
 
454
        view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
 
455
            self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
 
456
            delayed_graph_generation=delayed_graph_generation)
472
457
 
473
458
        # Apply the other filters
474
459
        return make_log_rev_iterator(self.branch, view_revisions,
481
466
        # Note that we always generate the merge revisions because
482
467
        # filter_revisions_touching_file_id() requires them ...
483
468
        rqst = self.rqst
484
 
        view_revisions = _calc_view_revisions(
485
 
            self.branch, self.start_rev_id, self.end_rev_id,
486
 
            rqst.get('direction'), generate_merge_revisions=True,
487
 
            exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
 
469
        view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
 
470
            self.end_rev_id, rqst.get('direction'), True)
488
471
        if not isinstance(view_revisions, list):
489
472
            view_revisions = list(view_revisions)
490
473
        view_revisions = _filter_revisions_touching_file_id(self.branch,
495
478
 
496
479
 
497
480
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
498
 
                         generate_merge_revisions,
499
 
                         delayed_graph_generation=False,
500
 
                         exclude_common_ancestry=False,
501
 
                         ):
 
481
    generate_merge_revisions, delayed_graph_generation=False):
502
482
    """Calculate the revisions to view.
503
483
 
504
484
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
505
485
             a list of the same tuples.
506
486
    """
507
 
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
508
 
        raise errors.BzrCommandError(
509
 
            '--exclude-common-ancestry requires two different revisions')
510
 
    if direction not in ('reverse', 'forward'):
511
 
        raise ValueError('invalid direction %r' % direction)
512
487
    br_revno, br_rev_id = branch.last_revision_info()
513
488
    if br_revno == 0:
514
489
        return []
515
490
 
516
 
    if (end_rev_id and start_rev_id == end_rev_id
517
 
        and (not generate_merge_revisions
518
 
             or not _has_merges(branch, end_rev_id))):
519
 
        # If a single revision is requested, check we can handle it
520
 
        iter_revs = _generate_one_revision(branch, end_rev_id, br_rev_id,
521
 
                                           br_revno)
522
 
    elif not generate_merge_revisions:
523
 
        # If we only want to see linear revisions, we can iterate ...
524
 
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
525
 
                                             direction)
526
 
        if direction == 'forward':
527
 
            iter_revs = reversed(iter_revs)
 
491
    # If a single revision is requested, check we can handle it
 
492
    generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
 
493
        (not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
 
494
    if generate_single_revision:
 
495
        return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
 
496
 
 
497
    # If we only want to see linear revisions, we can iterate ...
 
498
    if not generate_merge_revisions:
 
499
        return _generate_flat_revisions(branch, start_rev_id, end_rev_id,
 
500
            direction)
528
501
    else:
529
 
        iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
530
 
                                            direction, delayed_graph_generation,
531
 
                                            exclude_common_ancestry)
532
 
        if direction == 'forward':
533
 
            iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
534
 
    return iter_revs
 
502
        return _generate_all_revisions(branch, start_rev_id, end_rev_id,
 
503
            direction, delayed_graph_generation)
535
504
 
536
505
 
537
506
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
555
524
        except _StartNotLinearAncestor:
556
525
            raise errors.BzrCommandError('Start revision not found in'
557
526
                ' left-hand history of end revision.')
 
527
    if direction == 'forward':
 
528
        result = reversed(result)
558
529
    return result
559
530
 
560
531
 
561
532
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
562
 
                            delayed_graph_generation,
563
 
                            exclude_common_ancestry=False):
 
533
    delayed_graph_generation):
564
534
    # On large trees, generating the merge graph can take 30-60 seconds
565
535
    # so we delay doing it until a merge is detected, incrementally
566
536
    # returning initial (non-merge) revisions while we can.
567
 
 
568
 
    # The above is only true for old formats (<= 0.92), for newer formats, a
569
 
    # couple of seconds only should be needed to load the whole graph and the
570
 
    # other graph operations needed are even faster than that -- vila 100201
571
537
    initial_revisions = []
572
538
    if delayed_graph_generation:
573
539
        try:
574
 
            for rev_id, revno, depth in  _linear_view_revisions(
575
 
                branch, start_rev_id, end_rev_id):
 
540
            for rev_id, revno, depth in \
 
541
                _linear_view_revisions(branch, start_rev_id, end_rev_id):
576
542
                if _has_merges(branch, rev_id):
577
 
                    # The end_rev_id can be nested down somewhere. We need an
578
 
                    # explicit ancestry check. There is an ambiguity here as we
579
 
                    # may not raise _StartNotLinearAncestor for a revision that
580
 
                    # is an ancestor but not a *linear* one. But since we have
581
 
                    # loaded the graph to do the check (or calculate a dotted
582
 
                    # revno), we may as well accept to show the log...  We need
583
 
                    # the check only if start_rev_id is not None as all
584
 
                    # revisions have _mod_revision.NULL_REVISION as an ancestor
585
 
                    # -- vila 20100319
586
 
                    graph = branch.repository.get_graph()
587
 
                    if (start_rev_id is not None
588
 
                        and not graph.is_ancestor(start_rev_id, end_rev_id)):
589
 
                        raise _StartNotLinearAncestor()
590
 
                    # Since we collected the revisions so far, we need to
591
 
                    # adjust end_rev_id.
592
543
                    end_rev_id = rev_id
593
544
                    break
594
545
                else:
595
546
                    initial_revisions.append((rev_id, revno, depth))
596
547
            else:
597
548
                # No merged revisions found
598
 
                return initial_revisions
 
549
                if direction == 'reverse':
 
550
                    return initial_revisions
 
551
                elif direction == 'forward':
 
552
                    return reversed(initial_revisions)
 
553
                else:
 
554
                    raise ValueError('invalid direction %r' % direction)
599
555
        except _StartNotLinearAncestor:
600
556
            # A merge was never detected so the lower revision limit can't
601
557
            # be nested down somewhere
602
558
            raise errors.BzrCommandError('Start revision not found in'
603
559
                ' history of end revision.')
604
560
 
605
 
    # We exit the loop above because we encounter a revision with merges, from
606
 
    # this revision, we need to switch to _graph_view_revisions.
607
 
 
608
561
    # A log including nested merges is required. If the direction is reverse,
609
562
    # we rebase the initial merge depths so that the development line is
610
563
    # shown naturally, i.e. just like it is for linear logging. We can easily
612
565
    # indented at the end seems slightly nicer in that case.
613
566
    view_revisions = chain(iter(initial_revisions),
614
567
        _graph_view_revisions(branch, start_rev_id, end_rev_id,
615
 
                              rebase_initial_depths=(direction == 'reverse'),
616
 
                              exclude_common_ancestry=exclude_common_ancestry))
617
 
    return view_revisions
 
568
        rebase_initial_depths=direction == 'reverse'))
 
569
    if direction == 'reverse':
 
570
        return view_revisions
 
571
    elif direction == 'forward':
 
572
        # Forward means oldest first, adjusting for depth.
 
573
        view_revisions = reverse_by_depth(list(view_revisions))
 
574
        return _rebase_merge_depth(view_revisions)
 
575
    else:
 
576
        raise ValueError('invalid direction %r' % direction)
618
577
 
619
578
 
620
579
def _has_merges(branch, rev_id):
638
597
        else:
639
598
            # not obvious
640
599
            return False
641
 
    # if either start or end is not specified then we use either the first or
642
 
    # the last revision and *they* are obvious ancestors.
643
600
    return True
644
601
 
645
602
 
678
635
 
679
636
 
680
637
def _graph_view_revisions(branch, start_rev_id, end_rev_id,
681
 
                          rebase_initial_depths=True,
682
 
                          exclude_common_ancestry=False):
 
638
    rebase_initial_depths=True):
683
639
    """Calculate revisions to view including merges, newest to oldest.
684
640
 
685
641
    :param branch: the branch
689
645
      revision is found?
690
646
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
691
647
    """
692
 
    if exclude_common_ancestry:
693
 
        stop_rule = 'with-merges-without-common-ancestry'
694
 
    else:
695
 
        stop_rule = 'with-merges'
696
648
    view_revisions = branch.iter_merge_sorted_revisions(
697
649
        start_revision_id=end_rev_id, stop_revision_id=start_rev_id,
698
 
        stop_rule=stop_rule)
 
650
        stop_rule="with-merges")
699
651
    if not rebase_initial_depths:
700
652
        for (rev_id, merge_depth, revno, end_of_merge
701
653
             ) in view_revisions:
712
664
                depth_adjustment = merge_depth
713
665
            if depth_adjustment:
714
666
                if merge_depth < depth_adjustment:
715
 
                    # From now on we reduce the depth adjustement, this can be
716
 
                    # surprising for users. The alternative requires two passes
717
 
                    # which breaks the fast display of the first revision
718
 
                    # though.
719
667
                    depth_adjustment = merge_depth
720
668
                merge_depth -= depth_adjustment
721
669
            yield rev_id, '.'.join(map(str, revno)), merge_depth
722
670
 
723
671
 
724
 
@deprecated_function(deprecated_in((2, 2, 0)))
725
672
def calculate_view_revisions(branch, start_revision, end_revision, direction,
726
673
        specific_fileid, generate_merge_revisions):
727
674
    """Calculate the revisions to view.
729
676
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
730
677
             a list of the same tuples.
731
678
    """
 
679
    # This method is no longer called by the main code path.
 
680
    # It is retained for API compatibility and may be deprecated
 
681
    # soon. IGC 20090116
732
682
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
733
683
        end_revision)
734
684
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
1084
1034
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1085
1035
 
1086
1036
 
1087
 
@deprecated_function(deprecated_in((2, 2, 0)))
1088
1037
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
1089
1038
    """Filter view_revisions based on revision ranges.
1090
1039
 
1099
1048
 
1100
1049
    :return: The filtered view_revisions.
1101
1050
    """
 
1051
    # This method is no longer called by the main code path.
 
1052
    # It may be removed soon. IGC 20090127
1102
1053
    if start_rev_id or end_rev_id:
1103
1054
        revision_ids = [r for r, n, d in view_revisions]
1104
1055
        if start_rev_id:
1210
1161
    return result
1211
1162
 
1212
1163
 
1213
 
@deprecated_function(deprecated_in((2, 2, 0)))
1214
1164
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
1215
1165
                       include_merges=True):
1216
1166
    """Produce an iterator of revisions to show
1217
1167
    :return: an iterator of (revision_id, revno, merge_depth)
1218
1168
    (if there is no revno for a revision, None is supplied)
1219
1169
    """
 
1170
    # This method is no longer called by the main code path.
 
1171
    # It is retained for API compatibility and may be deprecated
 
1172
    # soon. IGC 20090127
1220
1173
    if not include_merges:
1221
1174
        revision_ids = mainline_revs[1:]
1222
1175
        if direction == 'reverse':
1340
1293
    preferred_levels = 0
1341
1294
 
1342
1295
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1343
 
                 delta_format=None, levels=None, show_advice=False,
1344
 
                 to_exact_file=None, author_list_handler=None):
 
1296
            delta_format=None, levels=None, show_advice=False):
1345
1297
        """Create a LogFormatter.
1346
1298
 
1347
1299
        :param to_file: the file to output to
1348
 
        :param to_exact_file: if set, gives an output stream to which 
1349
 
             non-Unicode diffs are written.
1350
1300
        :param show_ids: if True, revision-ids are to be displayed
1351
1301
        :param show_timezone: the timezone to use
1352
1302
        :param delta_format: the level of delta information to display
1355
1305
          let the log formatter decide.
1356
1306
        :param show_advice: whether to show advice at the end of the
1357
1307
          log or not
1358
 
        :param author_list_handler: callable generating a list of
1359
 
          authors to display for a given revision
1360
1308
        """
1361
1309
        self.to_file = to_file
1362
1310
        # 'exact' stream used to show diff, it should print content 'as is'
1363
1311
        # and should not try to decode/encode it to unicode to avoid bug #328007
1364
 
        if to_exact_file is not None:
1365
 
            self.to_exact_file = to_exact_file
1366
 
        else:
1367
 
            # XXX: somewhat hacky; this assumes it's a codec writer; it's better
1368
 
            # for code that expects to get diffs to pass in the exact file
1369
 
            # stream
1370
 
            self.to_exact_file = getattr(to_file, 'stream', to_file)
 
1312
        self.to_exact_file = getattr(to_file, 'stream', to_file)
1371
1313
        self.show_ids = show_ids
1372
1314
        self.show_timezone = show_timezone
1373
1315
        if delta_format is None:
1377
1319
        self.levels = levels
1378
1320
        self._show_advice = show_advice
1379
1321
        self._merge_count = 0
1380
 
        self._author_list_handler = author_list_handler
1381
1322
 
1382
1323
    def get_levels(self):
1383
1324
        """Get the number of levels to display or 0 for all."""
1415
1356
        return address
1416
1357
 
1417
1358
    def short_author(self, rev):
1418
 
        return self.authors(rev, 'first', short=True, sep=', ')
1419
 
 
1420
 
    def authors(self, rev, who, short=False, sep=None):
1421
 
        """Generate list of authors, taking --authors option into account.
1422
 
 
1423
 
        The caller has to specify the name of a author list handler,
1424
 
        as provided by the author list registry, using the ``who``
1425
 
        argument.  That name only sets a default, though: when the
1426
 
        user selected a different author list generation using the
1427
 
        ``--authors`` command line switch, as represented by the
1428
 
        ``author_list_handler`` constructor argument, that value takes
1429
 
        precedence.
1430
 
 
1431
 
        :param rev: The revision for which to generate the list of authors.
1432
 
        :param who: Name of the default handler.
1433
 
        :param short: Whether to shorten names to either name or address.
1434
 
        :param sep: What separator to use for automatic concatenation.
1435
 
        """
1436
 
        if self._author_list_handler is not None:
1437
 
            # The user did specify --authors, which overrides the default
1438
 
            author_list_handler = self._author_list_handler
1439
 
        else:
1440
 
            # The user didn't specify --authors, so we use the caller's default
1441
 
            author_list_handler = author_list_registry.get(who)
1442
 
        names = author_list_handler(rev)
1443
 
        if short:
1444
 
            for i in range(len(names)):
1445
 
                name, address = config.parse_username(names[i])
1446
 
                if name:
1447
 
                    names[i] = name
1448
 
                else:
1449
 
                    names[i] = address
1450
 
        if sep is not None:
1451
 
            names = sep.join(names)
1452
 
        return names
 
1359
        name, address = config.parse_username(rev.get_apparent_authors()[0])
 
1360
        if name:
 
1361
            return name
 
1362
        return address
1453
1363
 
1454
1364
    def merge_marker(self, revision):
1455
1365
        """Get the merge marker to include in the output or '' if none."""
1486
1396
        """
1487
1397
        # Revision comes directly from a foreign repository
1488
1398
        if isinstance(rev, foreign.ForeignRevision):
1489
 
            return self._format_properties(
1490
 
                rev.mapping.vcs.show_foreign_revid(rev.foreign_revid))
 
1399
            return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
1491
1400
 
1492
1401
        # Imported foreign revision revision ids always contain :
1493
1402
        if not ":" in rev.revision_id:
1557
1466
        lines.extend(self.custom_properties(revision.rev))
1558
1467
 
1559
1468
        committer = revision.rev.committer
1560
 
        authors = self.authors(revision.rev, 'all')
 
1469
        authors = revision.rev.get_apparent_authors()
1561
1470
        if authors != [committer]:
1562
1471
            lines.append('author: %s' % (", ".join(authors),))
1563
1472
        lines.append('committer: %s' % (committer,))
1580
1489
        to_file = self.to_file
1581
1490
        to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
1582
1491
        if revision.delta is not None:
1583
 
            # Use the standard status output to display changes
1584
 
            from bzrlib.delta import report_delta
1585
 
            report_delta(to_file, revision.delta, short_status=False, 
1586
 
                         show_ids=self.show_ids, indent=indent)
 
1492
            # We don't respect delta_format for compatibility
 
1493
            revision.delta.show(to_file, self.show_ids, indent=indent,
 
1494
                                short_status=False)
1587
1495
        if revision.diff is not None:
1588
1496
            to_file.write(indent + 'diff:\n')
1589
 
            to_file.flush()
1590
1497
            # Note: we explicitly don't indent the diff (relative to the
1591
1498
            # revision information) so that the output can be fed to patch -p0
1592
1499
            self.show_diff(self.to_exact_file, revision.diff, indent)
1593
 
            self.to_exact_file.flush()
1594
1500
 
1595
1501
    def get_advice_separator(self):
1596
1502
        """Get the text separating the log from the closing advice."""
1652
1558
                to_file.write(indent + offset + '%s\n' % (l,))
1653
1559
 
1654
1560
        if revision.delta is not None:
1655
 
            # Use the standard status output to display changes
1656
 
            from bzrlib.delta import report_delta
1657
 
            report_delta(to_file, revision.delta, 
1658
 
                         short_status=self.delta_format==1, 
1659
 
                         show_ids=self.show_ids, indent=indent + offset)
 
1561
            revision.delta.show(to_file, self.show_ids, indent=indent + offset,
 
1562
                                short_status=self.delta_format==1)
1660
1563
        if revision.diff is not None:
1661
1564
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1662
1565
        to_file.write('\n')
1670
1573
 
1671
1574
    def __init__(self, *args, **kwargs):
1672
1575
        super(LineLogFormatter, self).__init__(*args, **kwargs)
1673
 
        width = terminal_width()
1674
 
        if width is not None:
1675
 
            # we need one extra space for terminals that wrap on last char
1676
 
            width = width - 1
1677
 
        self._max_chars = width
 
1576
        self._max_chars = terminal_width() - 1
1678
1577
 
1679
1578
    def truncate(self, str, max_len):
1680
 
        if max_len is None or len(str) <= max_len:
 
1579
        if len(str) <= max_len:
1681
1580
            return str
1682
 
        return str[:max_len-3] + '...'
 
1581
        return str[:max_len-3]+'...'
1683
1582
 
1684
1583
    def date_string(self, rev):
1685
1584
        return format_date(rev.timestamp, rev.timezone or 0,
1737
1636
                               self.show_timezone,
1738
1637
                               date_fmt='%Y-%m-%d',
1739
1638
                               show_offset=False)
1740
 
        committer_str = self.authors(revision.rev, 'first', sep=', ')
1741
 
        committer_str = committer_str.replace(' <', '  <')
 
1639
        committer_str = revision.rev.committer.replace (' <', '  <')
1742
1640
        to_file.write('%s  %s\n\n' % (date_str,committer_str))
1743
1641
 
1744
1642
        if revision.delta is not None and revision.delta.has_changed():
1809
1707
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1810
1708
 
1811
1709
 
1812
 
def author_list_all(rev):
1813
 
    return rev.get_apparent_authors()[:]
1814
 
 
1815
 
 
1816
 
def author_list_first(rev):
1817
 
    lst = rev.get_apparent_authors()
1818
 
    try:
1819
 
        return [lst[0]]
1820
 
    except IndexError:
1821
 
        return []
1822
 
 
1823
 
 
1824
 
def author_list_committer(rev):
1825
 
    return [rev.committer]
1826
 
 
1827
 
 
1828
 
author_list_registry = registry.Registry()
1829
 
 
1830
 
author_list_registry.register('all', author_list_all,
1831
 
                              'All authors')
1832
 
 
1833
 
author_list_registry.register('first', author_list_first,
1834
 
                              'The first author')
1835
 
 
1836
 
author_list_registry.register('committer', author_list_committer,
1837
 
                              'The committer')
1838
 
 
1839
 
 
1840
1710
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1841
1711
    # deprecated; for compatibility
1842
1712
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1993
1863
        lf.log_revision(lr)
1994
1864
 
1995
1865
 
1996
 
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
 
1866
def _get_info_for_log_files(revisionspec_list, file_list):
1997
1867
    """Find file-ids and kinds given a list of files and a revision range.
1998
1868
 
1999
1869
    We search for files at the end of the range. If not found there,
2003
1873
    :param file_list: the list of paths given on the command line;
2004
1874
      the first of these can be a branch location or a file path,
2005
1875
      the remainder must be file paths
2006
 
    :param add_cleanup: When the branch returned is read locked,
2007
 
      an unlock call will be queued to the cleanup.
2008
1876
    :return: (branch, info_list, start_rev_info, end_rev_info) where
2009
1877
      info_list is a list of (relative_path, file_id, kind) tuples where
2010
1878
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2012
1880
    """
2013
1881
    from builtins import _get_revision_range, safe_relpath_files
2014
1882
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2015
 
    add_cleanup(b.lock_read().unlock)
 
1883
    b.lock_read()
2016
1884
    # XXX: It's damn messy converting a list of paths to relative paths when
2017
1885
    # those paths might be deleted ones, they might be on a case-insensitive
2018
1886
    # filesystem and/or they might be in silly locations (like another branch).
2097
1965
 
2098
1966
properties_handler_registry = registry.Registry()
2099
1967
 
2100
 
# Use the properties handlers to print out bug information if available
2101
 
def _bugs_properties_handler(revision):
2102
 
    if revision.properties.has_key('bugs'):
2103
 
        bug_lines = revision.properties['bugs'].split('\n')
2104
 
        bug_rows = [line.split(' ', 1) for line in bug_lines]
2105
 
        fixed_bug_urls = [row[0] for row in bug_rows if
2106
 
                          len(row) > 1 and row[1] == 'fixed']
2107
 
 
2108
 
        if fixed_bug_urls:
2109
 
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
2110
 
    return {}
2111
 
 
2112
 
properties_handler_registry.register('bugs_properties_handler',
2113
 
                                     _bugs_properties_handler)
2114
 
 
2115
1968
 
2116
1969
# adapters which revision ids to log are filtered. When log is called, the
2117
1970
# log_rev_iterator is adapted through each of these factory methods.