/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

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
70
70
    diff,
71
71
    errors,
72
72
    foreign,
 
73
    osutils,
73
74
    repository as _mod_repository,
74
75
    revision as _mod_revision,
75
76
    revisionspec,
85
86
    format_date,
86
87
    format_date_with_offset_in_original_timezone,
87
88
    get_terminal_encoding,
88
 
    re_compile_checked,
89
89
    terminal_width,
90
90
    )
91
91
from bzrlib.symbol_versioning import (
432
432
        else:
433
433
            specific_files = None
434
434
        s = StringIO()
 
435
        path_encoding = osutils.get_diff_header_encoding()
435
436
        diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
436
 
            new_label='')
 
437
            new_label='', path_encoding=path_encoding)
437
438
        return s.getvalue()
438
439
 
439
440
    def _create_log_revision_iterator(self):
522
523
    elif not generate_merge_revisions:
523
524
        # If we only want to see linear revisions, we can iterate ...
524
525
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
525
 
                                             direction)
 
526
                                             direction, exclude_common_ancestry)
526
527
        if direction == 'forward':
527
528
            iter_revs = reversed(iter_revs)
528
529
    else:
544
545
        return [(rev_id, revno_str, 0)]
545
546
 
546
547
 
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)
 
548
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
 
549
                             exclude_common_ancestry=False):
 
550
    result = _linear_view_revisions(
 
551
        branch, start_rev_id, end_rev_id,
 
552
        exclude_common_ancestry=exclude_common_ancestry)
549
553
    # If a start limit was given and it's not obviously an
550
554
    # ancestor of the end limit, check it before outputting anything
551
555
    if direction == 'forward' or (start_rev_id
572
576
    if delayed_graph_generation:
573
577
        try:
574
578
            for rev_id, revno, depth in  _linear_view_revisions(
575
 
                branch, start_rev_id, end_rev_id):
 
579
                branch, start_rev_id, end_rev_id, exclude_common_ancestry):
576
580
                if _has_merges(branch, rev_id):
577
581
                    # The end_rev_id can be nested down somewhere. We need an
578
582
                    # explicit ancestry check. There is an ambiguity here as we
643
647
    return True
644
648
 
645
649
 
646
 
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
 
650
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
 
651
                           exclude_common_ancestry=False):
647
652
    """Calculate a sequence of revisions to view, newest to oldest.
648
653
 
649
654
    :param start_rev_id: the lower revision-id
650
655
    :param end_rev_id: the upper revision-id
 
656
    :param exclude_common_ancestry: Whether the start_rev_id should be part of
 
657
        the iterated revisions.
651
658
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
652
659
    :raises _StartNotLinearAncestor: if a start_rev_id is specified but
653
 
      is not found walking the left-hand history
 
660
        is not found walking the left-hand history
654
661
    """
655
662
    br_revno, br_rev_id = branch.last_revision_info()
656
663
    repo = branch.repository
667
674
            revno = branch.revision_id_to_dotted_revno(revision_id)
668
675
            revno_str = '.'.join(str(n) for n in revno)
669
676
            if not found_start and revision_id == start_rev_id:
670
 
                yield revision_id, revno_str, 0
 
677
                if not exclude_common_ancestry:
 
678
                    yield revision_id, revno_str, 0
671
679
                found_start = True
672
680
                break
673
681
            else:
802
810
    """
803
811
    if search is None:
804
812
        return log_rev_iterator
805
 
    searchRE = re_compile_checked(search, re.IGNORECASE,
806
 
            'log message filter')
 
813
    searchRE = re.compile(search, re.IGNORECASE)
807
814
    return _filter_message_re(searchRE, log_rev_iterator)
808
815
 
809
816
 
1341
1348
 
1342
1349
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1343
1350
                 delta_format=None, levels=None, show_advice=False,
1344
 
                 to_exact_file=None):
 
1351
                 to_exact_file=None, author_list_handler=None):
1345
1352
        """Create a LogFormatter.
1346
1353
 
1347
1354
        :param to_file: the file to output to
1355
1362
          let the log formatter decide.
1356
1363
        :param show_advice: whether to show advice at the end of the
1357
1364
          log or not
 
1365
        :param author_list_handler: callable generating a list of
 
1366
          authors to display for a given revision
1358
1367
        """
1359
1368
        self.to_file = to_file
1360
1369
        # 'exact' stream used to show diff, it should print content 'as is'
1375
1384
        self.levels = levels
1376
1385
        self._show_advice = show_advice
1377
1386
        self._merge_count = 0
 
1387
        self._author_list_handler = author_list_handler
1378
1388
 
1379
1389
    def get_levels(self):
1380
1390
        """Get the number of levels to display or 0 for all."""
1412
1422
        return address
1413
1423
 
1414
1424
    def short_author(self, rev):
1415
 
        name, address = config.parse_username(rev.get_apparent_authors()[0])
1416
 
        if name:
1417
 
            return name
1418
 
        return address
 
1425
        return self.authors(rev, 'first', short=True, sep=', ')
 
1426
 
 
1427
    def authors(self, rev, who, short=False, sep=None):
 
1428
        """Generate list of authors, taking --authors option into account.
 
1429
 
 
1430
        The caller has to specify the name of a author list handler,
 
1431
        as provided by the author list registry, using the ``who``
 
1432
        argument.  That name only sets a default, though: when the
 
1433
        user selected a different author list generation using the
 
1434
        ``--authors`` command line switch, as represented by the
 
1435
        ``author_list_handler`` constructor argument, that value takes
 
1436
        precedence.
 
1437
 
 
1438
        :param rev: The revision for which to generate the list of authors.
 
1439
        :param who: Name of the default handler.
 
1440
        :param short: Whether to shorten names to either name or address.
 
1441
        :param sep: What separator to use for automatic concatenation.
 
1442
        """
 
1443
        if self._author_list_handler is not None:
 
1444
            # The user did specify --authors, which overrides the default
 
1445
            author_list_handler = self._author_list_handler
 
1446
        else:
 
1447
            # The user didn't specify --authors, so we use the caller's default
 
1448
            author_list_handler = author_list_registry.get(who)
 
1449
        names = author_list_handler(rev)
 
1450
        if short:
 
1451
            for i in range(len(names)):
 
1452
                name, address = config.parse_username(names[i])
 
1453
                if name:
 
1454
                    names[i] = name
 
1455
                else:
 
1456
                    names[i] = address
 
1457
        if sep is not None:
 
1458
            names = sep.join(names)
 
1459
        return names
1419
1460
 
1420
1461
    def merge_marker(self, revision):
1421
1462
        """Get the merge marker to include in the output or '' if none."""
1523
1564
        lines.extend(self.custom_properties(revision.rev))
1524
1565
 
1525
1566
        committer = revision.rev.committer
1526
 
        authors = revision.rev.get_apparent_authors()
 
1567
        authors = self.authors(revision.rev, 'all')
1527
1568
        if authors != [committer]:
1528
1569
            lines.append('author: %s' % (", ".join(authors),))
1529
1570
        lines.append('committer: %s' % (committer,))
1703
1744
                               self.show_timezone,
1704
1745
                               date_fmt='%Y-%m-%d',
1705
1746
                               show_offset=False)
1706
 
        committer_str = revision.rev.get_apparent_authors()[0].replace (' <', '  <')
 
1747
        committer_str = self.authors(revision.rev, 'first', sep=', ')
 
1748
        committer_str = committer_str.replace(' <', '  <')
1707
1749
        to_file.write('%s  %s\n\n' % (date_str,committer_str))
1708
1750
 
1709
1751
        if revision.delta is not None and revision.delta.has_changed():
1774
1816
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1775
1817
 
1776
1818
 
 
1819
def author_list_all(rev):
 
1820
    return rev.get_apparent_authors()[:]
 
1821
 
 
1822
 
 
1823
def author_list_first(rev):
 
1824
    lst = rev.get_apparent_authors()
 
1825
    try:
 
1826
        return [lst[0]]
 
1827
    except IndexError:
 
1828
        return []
 
1829
 
 
1830
 
 
1831
def author_list_committer(rev):
 
1832
    return [rev.committer]
 
1833
 
 
1834
 
 
1835
author_list_registry = registry.Registry()
 
1836
 
 
1837
author_list_registry.register('all', author_list_all,
 
1838
                              'All authors')
 
1839
 
 
1840
author_list_registry.register('first', author_list_first,
 
1841
                              'The first author')
 
1842
 
 
1843
author_list_registry.register('committer', author_list_committer,
 
1844
                              'The committer')
 
1845
 
 
1846
 
1777
1847
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1778
1848
    # deprecated; for compatibility
1779
1849
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1930
2000
        lf.log_revision(lr)
1931
2001
 
1932
2002
 
1933
 
def _get_info_for_log_files(revisionspec_list, file_list):
 
2003
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
1934
2004
    """Find file-ids and kinds given a list of files and a revision range.
1935
2005
 
1936
2006
    We search for files at the end of the range. If not found there,
1940
2010
    :param file_list: the list of paths given on the command line;
1941
2011
      the first of these can be a branch location or a file path,
1942
2012
      the remainder must be file paths
 
2013
    :param add_cleanup: When the branch returned is read locked,
 
2014
      an unlock call will be queued to the cleanup.
1943
2015
    :return: (branch, info_list, start_rev_info, end_rev_info) where
1944
2016
      info_list is a list of (relative_path, file_id, kind) tuples where
1945
2017
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
1946
2018
      branch will be read-locked.
1947
2019
    """
1948
 
    from builtins import _get_revision_range, safe_relpath_files
 
2020
    from builtins import _get_revision_range
1949
2021
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
1950
 
    b.lock_read()
 
2022
    add_cleanup(b.lock_read().unlock)
1951
2023
    # XXX: It's damn messy converting a list of paths to relative paths when
1952
2024
    # those paths might be deleted ones, they might be on a case-insensitive
1953
2025
    # filesystem and/or they might be in silly locations (like another branch).
1957
2029
    # case of running log in a nested directory, assuming paths beyond the
1958
2030
    # first one haven't been deleted ...
1959
2031
    if tree:
1960
 
        relpaths = [path] + safe_relpath_files(tree, file_list[1:])
 
2032
        relpaths = [path] + tree.safe_relpath_files(file_list[1:])
1961
2033
    else:
1962
2034
        relpaths = [path] + file_list[1:]
1963
2035
    info_list = []