/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: Vincent Ladeuil
  • Date: 2011-06-16 10:45:17 UTC
  • mto: This revision was merged to the branch mainline in revision 5981.
  • Revision ID: v.ladeuil+lp@free.fr-20110616104517-4qzhmzkxgozji88y
Add copyright notice, some docs and some cleanups.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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,
77
76
    tsort,
78
77
    )
79
78
""")
84
83
from bzrlib.osutils import (
85
84
    format_date,
86
85
    format_date_with_offset_in_original_timezone,
 
86
    get_diff_header_encoding,
87
87
    get_terminal_encoding,
88
 
    re_compile_checked,
89
88
    terminal_width,
90
89
    )
91
90
from bzrlib.symbol_versioning import (
298
297
 
299
298
def _apply_log_request_defaults(rqst):
300
299
    """Apply default values to a request dictionary."""
301
 
    result = _DEFAULT_REQUEST_PARAMS
 
300
    result = _DEFAULT_REQUEST_PARAMS.copy()
302
301
    if rqst:
303
302
        result.update(rqst)
304
303
    return result
432
431
        else:
433
432
            specific_files = None
434
433
        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='')
 
436
            new_label='', path_encoding=path_encoding)
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)
 
525
                                             direction, exclude_common_ancestry)
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 = branch.revision_id_to_dotted_revno(rev_id)
543
 
        revno_str = '.'.join(str(n) for n in revno)
 
542
        revno_str = _compute_revno_str(branch, rev_id)
544
543
        return [(rev_id, revno_str, 0)]
545
544
 
546
545
 
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)
 
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)
549
551
    # If a start limit was given and it's not obviously an
550
552
    # ancestor of the end limit, check it before outputting anything
551
553
    if direction == 'forward' or (start_rev_id
572
574
    if delayed_graph_generation:
573
575
        try:
574
576
            for rev_id, revno, depth in  _linear_view_revisions(
575
 
                branch, start_rev_id, end_rev_id):
 
577
                branch, start_rev_id, end_rev_id, exclude_common_ancestry):
576
578
                if _has_merges(branch, rev_id):
577
579
                    # The end_rev_id can be nested down somewhere. We need an
578
580
                    # explicit ancestry check. There is an ambiguity here as we
623
625
    return len(parents) > 1
624
626
 
625
627
 
 
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
 
626
643
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
627
644
    """Is start_rev_id an obvious ancestor of end_rev_id?"""
628
645
    if start_rev_id and end_rev_id:
629
 
        start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
630
 
        end_dotted = branch.revision_id_to_dotted_revno(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
631
652
        if len(start_dotted) == 1 and len(end_dotted) == 1:
632
653
            # both on mainline
633
654
            return start_dotted[0] <= end_dotted[0]
643
664
    return True
644
665
 
645
666
 
646
 
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
 
667
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
 
668
                           exclude_common_ancestry=False):
647
669
    """Calculate a sequence of revisions to view, newest to oldest.
648
670
 
649
671
    :param start_rev_id: the lower revision-id
650
672
    :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.
651
675
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
652
676
    :raises _StartNotLinearAncestor: if a start_rev_id is specified but
653
 
      is not found walking the left-hand history
 
677
        is not found walking the left-hand history
654
678
    """
655
679
    br_revno, br_rev_id = branch.last_revision_info()
656
680
    repo = branch.repository
664
688
            end_rev_id = br_rev_id
665
689
        found_start = start_rev_id is None
666
690
        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)
 
691
            revno_str = _compute_revno_str(branch, revision_id)
669
692
            if not found_start and revision_id == start_rev_id:
670
 
                yield revision_id, revno_str, 0
 
693
                if not exclude_common_ancestry:
 
694
                    yield revision_id, revno_str, 0
671
695
                found_start = True
672
696
                break
673
697
            else:
802
826
    """
803
827
    if search is None:
804
828
        return log_rev_iterator
805
 
    searchRE = re_compile_checked(search, re.IGNORECASE,
806
 
            'log message filter')
 
829
    searchRE = re.compile(search, re.IGNORECASE)
807
830
    return _filter_message_re(searchRE, log_rev_iterator)
808
831
 
809
832
 
1136
1159
    This includes the revisions which directly change the file id,
1137
1160
    and the revisions which merge these changes. So if the
1138
1161
    revision graph is::
 
1162
 
1139
1163
        A-.
1140
1164
        |\ \
1141
1165
        B C E
1168
1192
    """
1169
1193
    # Lookup all possible text keys to determine which ones actually modified
1170
1194
    # the file.
 
1195
    graph = branch.repository.get_file_graph()
 
1196
    get_parent_map = graph.get_parent_map
1171
1197
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1172
1198
    next_keys = None
1173
1199
    # Looking up keys in batches of 1000 can cut the time in half, as well as
1177
1203
    #       indexing layer. We might consider passing in hints as to the known
1178
1204
    #       access pattern (sparse/clustered, high success rate/low success
1179
1205
    #       rate). This particular access is clustered with a low success rate.
1180
 
    get_parent_map = branch.repository.texts.get_parent_map
1181
1206
    modified_text_revisions = set()
1182
1207
    chunk_size = 1000
1183
1208
    for start in xrange(0, len(text_keys), chunk_size):
1293
1318
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1294
1319
                 tags=None, diff=None):
1295
1320
        self.rev = rev
1296
 
        self.revno = str(revno)
 
1321
        if revno is None:
 
1322
            self.revno = None
 
1323
        else:
 
1324
            self.revno = str(revno)
1297
1325
        self.merge_depth = merge_depth
1298
1326
        self.delta = delta
1299
1327
        self.tags = tags
1312
1340
    to indicate which LogRevision attributes it supports:
1313
1341
 
1314
1342
    - supports_delta must be True if this log formatter supports delta.
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.
 
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.
1318
1346
 
1319
1347
    - supports_merge_revisions must be True if this log formatter supports
1320
 
        merge revisions.  If not, then only mainline revisions will be passed
1321
 
        to the formatter.
 
1348
      merge revisions.  If not, then only mainline revisions will be passed
 
1349
      to the formatter.
1322
1350
 
1323
1351
    - preferred_levels is the number of levels this formatter defaults to.
1324
 
        The default value is zero meaning display all levels.
1325
 
        This value is only relevant if supports_merge_revisions is True.
 
1352
      The default value is zero meaning display all levels.
 
1353
      This value is only relevant if supports_merge_revisions is True.
1326
1354
 
1327
1355
    - supports_tags must be True if this log formatter supports tags.
1328
 
        Otherwise the tags attribute may not be populated.
 
1356
      Otherwise the tags attribute may not be populated.
1329
1357
 
1330
1358
    - supports_diff must be True if this log formatter supports diffs.
1331
 
        Otherwise the diff attribute may not be populated.
 
1359
      Otherwise the diff attribute may not be populated.
1332
1360
 
1333
1361
    Plugins can register functions to show custom revision properties using
1334
1362
    the properties_handler_registry. The registered function
1335
 
    must respect the following interface description:
 
1363
    must respect the following interface description::
 
1364
 
1336
1365
        def my_show_properties(properties_dict):
1337
1366
            # code that returns a dict {'name':'value'} of the properties
1338
1367
            # to be shown
1550
1579
                self.merge_marker(revision)))
1551
1580
        if revision.tags:
1552
1581
            lines.append('tags: %s' % (', '.join(revision.tags)))
1553
 
        if self.show_ids:
 
1582
        if self.show_ids or revision.revno is None:
1554
1583
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
 
1584
        if self.show_ids:
1555
1585
            for parent_id in revision.rev.parent_ids:
1556
1586
                lines.append('parent: %s' % (parent_id,))
1557
1587
        lines.extend(self.custom_properties(revision.rev))
1620
1650
        indent = '    ' * depth
1621
1651
        revno_width = self.revno_width_by_depth.get(depth)
1622
1652
        if revno_width is None:
1623
 
            if revision.revno.find('.') == -1:
 
1653
            if revision.revno is None or revision.revno.find('.') == -1:
1624
1654
                # mainline revno, e.g. 12345
1625
1655
                revno_width = 5
1626
1656
            else:
1634
1664
        if revision.tags:
1635
1665
            tags = ' {%s}' % (', '.join(revision.tags))
1636
1666
        to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1637
 
                revision.revno, self.short_author(revision.rev),
 
1667
                revision.revno or "", self.short_author(revision.rev),
1638
1668
                format_date(revision.rev.timestamp,
1639
1669
                            revision.rev.timezone or 0,
1640
1670
                            self.show_timezone, date_fmt="%Y-%m-%d",
1641
1671
                            show_offset=False),
1642
1672
                tags, self.merge_marker(revision)))
1643
1673
        self.show_properties(revision.rev, indent+offset)
1644
 
        if self.show_ids:
 
1674
        if self.show_ids or revision.revno is None:
1645
1675
            to_file.write(indent + offset + 'revision-id:%s\n'
1646
1676
                          % (revision.rev.revision_id,))
1647
1677
        if not revision.rev.message:
1700
1730
 
1701
1731
    def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1702
1732
        """Format log info into one string. Truncate tail of string
1703
 
        :param  revno:      revision number or None.
1704
 
                            Revision numbers counts from 1.
1705
 
        :param  rev:        revision object
1706
 
        :param  max_chars:  maximum length of resulting string
1707
 
        :param  tags:       list of tags or None
1708
 
        :param  prefix:     string to prefix each line
1709
 
        :return:            formatted truncated 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
1710
1741
        """
1711
1742
        out = []
1712
1743
        if revno:
1713
1744
            # show revno only when is not None
1714
1745
            out.append("%s:" % revno)
1715
 
        out.append(self.truncate(self.short_author(rev), 20))
 
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))
1716
1750
        out.append(self.date_string(rev))
1717
1751
        if len(rev.parent_ids) > 1:
1718
1752
            out.append('[merge]')
2010
2044
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2011
2045
      branch will be read-locked.
2012
2046
    """
2013
 
    from builtins import _get_revision_range, safe_relpath_files
 
2047
    from builtins import _get_revision_range
2014
2048
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2015
2049
    add_cleanup(b.lock_read().unlock)
2016
2050
    # XXX: It's damn messy converting a list of paths to relative paths when
2022
2056
    # case of running log in a nested directory, assuming paths beyond the
2023
2057
    # first one haven't been deleted ...
2024
2058
    if tree:
2025
 
        relpaths = [path] + safe_relpath_files(tree, file_list[1:])
 
2059
        relpaths = [path] + tree.safe_relpath_files(file_list[1:])
2026
2060
    else:
2027
2061
        relpaths = [path] + file_list[1:]
2028
2062
    info_list = []