/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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
78
    )
78
79
""")
83
84
from bzrlib.osutils import (
84
85
    format_date,
85
86
    format_date_with_offset_in_original_timezone,
86
 
    get_diff_header_encoding,
87
87
    get_terminal_encoding,
 
88
    re_compile_checked,
88
89
    terminal_width,
89
90
    )
90
91
from bzrlib.symbol_versioning import (
297
298
 
298
299
def _apply_log_request_defaults(rqst):
299
300
    """Apply default values to a request dictionary."""
300
 
    result = _DEFAULT_REQUEST_PARAMS.copy()
 
301
    result = _DEFAULT_REQUEST_PARAMS
301
302
    if rqst:
302
303
        result.update(rqst)
303
304
    return result
431
432
        else:
432
433
            specific_files = None
433
434
        s = StringIO()
434
 
        path_encoding = get_diff_header_encoding()
435
435
        diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
436
 
            new_label='', path_encoding=path_encoding)
 
436
            new_label='')
437
437
        return s.getvalue()
438
438
 
439
439
    def _create_log_revision_iterator(self):
522
522
    elif not generate_merge_revisions:
523
523
        # If we only want to see linear revisions, we can iterate ...
524
524
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
525
 
                                             direction, exclude_common_ancestry)
 
525
                                             direction)
526
526
        if direction == 'forward':
527
527
            iter_revs = reversed(iter_revs)
528
528
    else:
539
539
        # It's the tip
540
540
        return [(br_rev_id, br_revno, 0)]
541
541
    else:
542
 
        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)
543
544
        return [(rev_id, revno_str, 0)]
544
545
 
545
546
 
546
 
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
547
 
                             exclude_common_ancestry=False):
548
 
    result = _linear_view_revisions(
549
 
        branch, start_rev_id, end_rev_id,
550
 
        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)
551
549
    # If a start limit was given and it's not obviously an
552
550
    # ancestor of the end limit, check it before outputting anything
553
551
    if direction == 'forward' or (start_rev_id
574
572
    if delayed_graph_generation:
575
573
        try:
576
574
            for rev_id, revno, depth in  _linear_view_revisions(
577
 
                branch, start_rev_id, end_rev_id, exclude_common_ancestry):
 
575
                branch, start_rev_id, end_rev_id):
578
576
                if _has_merges(branch, rev_id):
579
577
                    # The end_rev_id can be nested down somewhere. We need an
580
578
                    # explicit ancestry check. There is an ambiguity here as we
625
623
    return len(parents) > 1
626
624
 
627
625
 
628
 
def _compute_revno_str(branch, rev_id):
629
 
    """Compute the revno string from a rev_id.
630
 
 
631
 
    :return: The revno string, or None if the revision is not in the supplied
632
 
        branch.
633
 
    """
634
 
    try:
635
 
        revno = branch.revision_id_to_dotted_revno(rev_id)
636
 
    except errors.NoSuchRevision:
637
 
        # The revision must be outside of this branch
638
 
        return None
639
 
    else:
640
 
        return '.'.join(str(n) for n in revno)
641
 
 
642
 
 
643
626
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
644
627
    """Is start_rev_id an obvious ancestor of end_rev_id?"""
645
628
    if start_rev_id and end_rev_id:
646
 
        try:
647
 
            start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
648
 
            end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
649
 
        except errors.NoSuchRevision:
650
 
            # one or both is not in the branch; not obvious
651
 
            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)
652
631
        if len(start_dotted) == 1 and len(end_dotted) == 1:
653
632
            # both on mainline
654
633
            return start_dotted[0] <= end_dotted[0]
664
643
    return True
665
644
 
666
645
 
667
 
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
668
 
                           exclude_common_ancestry=False):
 
646
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
669
647
    """Calculate a sequence of revisions to view, newest to oldest.
670
648
 
671
649
    :param start_rev_id: the lower revision-id
672
650
    :param end_rev_id: the upper revision-id
673
 
    :param exclude_common_ancestry: Whether the start_rev_id should be part of
674
 
        the iterated revisions.
675
651
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
676
652
    :raises _StartNotLinearAncestor: if a start_rev_id is specified but
677
 
        is not found walking the left-hand history
 
653
      is not found walking the left-hand history
678
654
    """
679
655
    br_revno, br_rev_id = branch.last_revision_info()
680
656
    repo = branch.repository
688
664
            end_rev_id = br_rev_id
689
665
        found_start = start_rev_id is None
690
666
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
691
 
            revno_str = _compute_revno_str(branch, revision_id)
 
667
            revno = branch.revision_id_to_dotted_revno(revision_id)
 
668
            revno_str = '.'.join(str(n) for n in revno)
692
669
            if not found_start and revision_id == start_rev_id:
693
 
                if not exclude_common_ancestry:
694
 
                    yield revision_id, revno_str, 0
 
670
                yield revision_id, revno_str, 0
695
671
                found_start = True
696
672
                break
697
673
            else:
826
802
    """
827
803
    if search is None:
828
804
        return log_rev_iterator
829
 
    searchRE = re.compile(search, re.IGNORECASE)
 
805
    searchRE = re_compile_checked(search, re.IGNORECASE,
 
806
            'log message filter')
830
807
    return _filter_message_re(searchRE, log_rev_iterator)
831
808
 
832
809
 
1159
1136
    This includes the revisions which directly change the file id,
1160
1137
    and the revisions which merge these changes. So if the
1161
1138
    revision graph is::
1162
 
 
1163
1139
        A-.
1164
1140
        |\ \
1165
1141
        B C E
1192
1168
    """
1193
1169
    # Lookup all possible text keys to determine which ones actually modified
1194
1170
    # the file.
1195
 
    graph = branch.repository.get_file_graph()
1196
 
    get_parent_map = graph.get_parent_map
1197
1171
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1198
1172
    next_keys = None
1199
1173
    # Looking up keys in batches of 1000 can cut the time in half, as well as
1203
1177
    #       indexing layer. We might consider passing in hints as to the known
1204
1178
    #       access pattern (sparse/clustered, high success rate/low success
1205
1179
    #       rate). This particular access is clustered with a low success rate.
 
1180
    get_parent_map = branch.repository.texts.get_parent_map
1206
1181
    modified_text_revisions = set()
1207
1182
    chunk_size = 1000
1208
1183
    for start in xrange(0, len(text_keys), chunk_size):
1318
1293
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1319
1294
                 tags=None, diff=None):
1320
1295
        self.rev = rev
1321
 
        if revno is None:
1322
 
            self.revno = None
1323
 
        else:
1324
 
            self.revno = str(revno)
 
1296
        self.revno = str(revno)
1325
1297
        self.merge_depth = merge_depth
1326
1298
        self.delta = delta
1327
1299
        self.tags = tags
1340
1312
    to indicate which LogRevision attributes it supports:
1341
1313
 
1342
1314
    - supports_delta must be True if this log formatter supports delta.
1343
 
      Otherwise the delta attribute may not be populated.  The 'delta_format'
1344
 
      attribute describes whether the 'short_status' format (1) or the long
1345
 
      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.
1346
1318
 
1347
1319
    - supports_merge_revisions must be True if this log formatter supports
1348
 
      merge revisions.  If not, then only mainline revisions will be passed
1349
 
      to the formatter.
 
1320
        merge revisions.  If not, then only mainline revisions will be passed
 
1321
        to the formatter.
1350
1322
 
1351
1323
    - preferred_levels is the number of levels this formatter defaults to.
1352
 
      The default value is zero meaning display all levels.
1353
 
      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.
1354
1326
 
1355
1327
    - supports_tags must be True if this log formatter supports tags.
1356
 
      Otherwise the tags attribute may not be populated.
 
1328
        Otherwise the tags attribute may not be populated.
1357
1329
 
1358
1330
    - supports_diff must be True if this log formatter supports diffs.
1359
 
      Otherwise the diff attribute may not be populated.
 
1331
        Otherwise the diff attribute may not be populated.
1360
1332
 
1361
1333
    Plugins can register functions to show custom revision properties using
1362
1334
    the properties_handler_registry. The registered function
1363
 
    must respect the following interface description::
1364
 
 
 
1335
    must respect the following interface description:
1365
1336
        def my_show_properties(properties_dict):
1366
1337
            # code that returns a dict {'name':'value'} of the properties
1367
1338
            # to be shown
1370
1341
 
1371
1342
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1372
1343
                 delta_format=None, levels=None, show_advice=False,
1373
 
                 to_exact_file=None, author_list_handler=None):
 
1344
                 to_exact_file=None):
1374
1345
        """Create a LogFormatter.
1375
1346
 
1376
1347
        :param to_file: the file to output to
1384
1355
          let the log formatter decide.
1385
1356
        :param show_advice: whether to show advice at the end of the
1386
1357
          log or not
1387
 
        :param author_list_handler: callable generating a list of
1388
 
          authors to display for a given revision
1389
1358
        """
1390
1359
        self.to_file = to_file
1391
1360
        # 'exact' stream used to show diff, it should print content 'as is'
1406
1375
        self.levels = levels
1407
1376
        self._show_advice = show_advice
1408
1377
        self._merge_count = 0
1409
 
        self._author_list_handler = author_list_handler
1410
1378
 
1411
1379
    def get_levels(self):
1412
1380
        """Get the number of levels to display or 0 for all."""
1444
1412
        return address
1445
1413
 
1446
1414
    def short_author(self, rev):
1447
 
        return self.authors(rev, 'first', short=True, sep=', ')
1448
 
 
1449
 
    def authors(self, rev, who, short=False, sep=None):
1450
 
        """Generate list of authors, taking --authors option into account.
1451
 
 
1452
 
        The caller has to specify the name of a author list handler,
1453
 
        as provided by the author list registry, using the ``who``
1454
 
        argument.  That name only sets a default, though: when the
1455
 
        user selected a different author list generation using the
1456
 
        ``--authors`` command line switch, as represented by the
1457
 
        ``author_list_handler`` constructor argument, that value takes
1458
 
        precedence.
1459
 
 
1460
 
        :param rev: The revision for which to generate the list of authors.
1461
 
        :param who: Name of the default handler.
1462
 
        :param short: Whether to shorten names to either name or address.
1463
 
        :param sep: What separator to use for automatic concatenation.
1464
 
        """
1465
 
        if self._author_list_handler is not None:
1466
 
            # The user did specify --authors, which overrides the default
1467
 
            author_list_handler = self._author_list_handler
1468
 
        else:
1469
 
            # The user didn't specify --authors, so we use the caller's default
1470
 
            author_list_handler = author_list_registry.get(who)
1471
 
        names = author_list_handler(rev)
1472
 
        if short:
1473
 
            for i in range(len(names)):
1474
 
                name, address = config.parse_username(names[i])
1475
 
                if name:
1476
 
                    names[i] = name
1477
 
                else:
1478
 
                    names[i] = address
1479
 
        if sep is not None:
1480
 
            names = sep.join(names)
1481
 
        return names
 
1415
        name, address = config.parse_username(rev.get_apparent_authors()[0])
 
1416
        if name:
 
1417
            return name
 
1418
        return address
1482
1419
 
1483
1420
    def merge_marker(self, revision):
1484
1421
        """Get the merge marker to include in the output or '' if none."""
1579
1516
                self.merge_marker(revision)))
1580
1517
        if revision.tags:
1581
1518
            lines.append('tags: %s' % (', '.join(revision.tags)))
1582
 
        if self.show_ids or revision.revno is None:
 
1519
        if self.show_ids:
1583
1520
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
1584
 
        if self.show_ids:
1585
1521
            for parent_id in revision.rev.parent_ids:
1586
1522
                lines.append('parent: %s' % (parent_id,))
1587
1523
        lines.extend(self.custom_properties(revision.rev))
1588
1524
 
1589
1525
        committer = revision.rev.committer
1590
 
        authors = self.authors(revision.rev, 'all')
 
1526
        authors = revision.rev.get_apparent_authors()
1591
1527
        if authors != [committer]:
1592
1528
            lines.append('author: %s' % (", ".join(authors),))
1593
1529
        lines.append('committer: %s' % (committer,))
1650
1586
        indent = '    ' * depth
1651
1587
        revno_width = self.revno_width_by_depth.get(depth)
1652
1588
        if revno_width is None:
1653
 
            if revision.revno is None or revision.revno.find('.') == -1:
 
1589
            if revision.revno.find('.') == -1:
1654
1590
                # mainline revno, e.g. 12345
1655
1591
                revno_width = 5
1656
1592
            else:
1664
1600
        if revision.tags:
1665
1601
            tags = ' {%s}' % (', '.join(revision.tags))
1666
1602
        to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1667
 
                revision.revno or "", self.short_author(revision.rev),
 
1603
                revision.revno, self.short_author(revision.rev),
1668
1604
                format_date(revision.rev.timestamp,
1669
1605
                            revision.rev.timezone or 0,
1670
1606
                            self.show_timezone, date_fmt="%Y-%m-%d",
1671
1607
                            show_offset=False),
1672
1608
                tags, self.merge_marker(revision)))
1673
1609
        self.show_properties(revision.rev, indent+offset)
1674
 
        if self.show_ids or revision.revno is None:
 
1610
        if self.show_ids:
1675
1611
            to_file.write(indent + offset + 'revision-id:%s\n'
1676
1612
                          % (revision.rev.revision_id,))
1677
1613
        if not revision.rev.message:
1730
1666
 
1731
1667
    def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1732
1668
        """Format log info into one string. Truncate tail of string
1733
 
 
1734
 
        :param revno:      revision number or None.
1735
 
                           Revision numbers counts from 1.
1736
 
        :param rev:        revision object
1737
 
        :param max_chars:  maximum length of resulting string
1738
 
        :param tags:       list of tags or None
1739
 
        :param prefix:     string to prefix each line
1740
 
        :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
1741
1676
        """
1742
1677
        out = []
1743
1678
        if revno:
1744
1679
            # show revno only when is not None
1745
1680
            out.append("%s:" % revno)
1746
 
        if max_chars is not None:
1747
 
            out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1748
 
        else:
1749
 
            out.append(self.short_author(rev))
 
1681
        out.append(self.truncate(self.short_author(rev), 20))
1750
1682
        out.append(self.date_string(rev))
1751
1683
        if len(rev.parent_ids) > 1:
1752
1684
            out.append('[merge]')
1771
1703
                               self.show_timezone,
1772
1704
                               date_fmt='%Y-%m-%d',
1773
1705
                               show_offset=False)
1774
 
        committer_str = self.authors(revision.rev, 'first', sep=', ')
1775
 
        committer_str = committer_str.replace(' <', '  <')
 
1706
        committer_str = revision.rev.get_apparent_authors()[0].replace (' <', '  <')
1776
1707
        to_file.write('%s  %s\n\n' % (date_str,committer_str))
1777
1708
 
1778
1709
        if revision.delta is not None and revision.delta.has_changed():
1843
1774
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1844
1775
 
1845
1776
 
1846
 
def author_list_all(rev):
1847
 
    return rev.get_apparent_authors()[:]
1848
 
 
1849
 
 
1850
 
def author_list_first(rev):
1851
 
    lst = rev.get_apparent_authors()
1852
 
    try:
1853
 
        return [lst[0]]
1854
 
    except IndexError:
1855
 
        return []
1856
 
 
1857
 
 
1858
 
def author_list_committer(rev):
1859
 
    return [rev.committer]
1860
 
 
1861
 
 
1862
 
author_list_registry = registry.Registry()
1863
 
 
1864
 
author_list_registry.register('all', author_list_all,
1865
 
                              'All authors')
1866
 
 
1867
 
author_list_registry.register('first', author_list_first,
1868
 
                              'The first author')
1869
 
 
1870
 
author_list_registry.register('committer', author_list_committer,
1871
 
                              'The committer')
1872
 
 
1873
 
 
1874
1777
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1875
1778
    # deprecated; for compatibility
1876
1779
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
2027
1930
        lf.log_revision(lr)
2028
1931
 
2029
1932
 
2030
 
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
 
1933
def _get_info_for_log_files(revisionspec_list, file_list):
2031
1934
    """Find file-ids and kinds given a list of files and a revision range.
2032
1935
 
2033
1936
    We search for files at the end of the range. If not found there,
2037
1940
    :param file_list: the list of paths given on the command line;
2038
1941
      the first of these can be a branch location or a file path,
2039
1942
      the remainder must be file paths
2040
 
    :param add_cleanup: When the branch returned is read locked,
2041
 
      an unlock call will be queued to the cleanup.
2042
1943
    :return: (branch, info_list, start_rev_info, end_rev_info) where
2043
1944
      info_list is a list of (relative_path, file_id, kind) tuples where
2044
1945
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2045
1946
      branch will be read-locked.
2046
1947
    """
2047
 
    from builtins import _get_revision_range
 
1948
    from builtins import _get_revision_range, safe_relpath_files
2048
1949
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2049
 
    add_cleanup(b.lock_read().unlock)
 
1950
    b.lock_read()
2050
1951
    # XXX: It's damn messy converting a list of paths to relative paths when
2051
1952
    # those paths might be deleted ones, they might be on a case-insensitive
2052
1953
    # filesystem and/or they might be in silly locations (like another branch).
2056
1957
    # case of running log in a nested directory, assuming paths beyond the
2057
1958
    # first one haven't been deleted ...
2058
1959
    if tree:
2059
 
        relpaths = [path] + tree.safe_relpath_files(file_list[1:])
 
1960
        relpaths = [path] + safe_relpath_files(tree, file_list[1:])
2060
1961
    else:
2061
1962
        relpaths = [path] + file_list[1:]
2062
1963
    info_list = []