~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Vincent Ladeuil
  • Date: 2010-01-25 15:55:48 UTC
  • mto: (4985.1.4 add-attr-cleanup)
  • mto: This revision was merged to the branch mainline in revision 4988.
  • Revision ID: v.ladeuil+lp@free.fr-20100125155548-0l352pujvt5bzl5e
Deploy addAttrCleanup on the whole test suite.

Several use case worth mentioning:

- setting a module or any other object attribute is the majority
by far. In some cases the setting itself is deferred but most of
the time we want to set at the same time we add the cleanup.

- there multiple occurrences of protecting hooks or ui factory
which are now useless (the test framework takes care of that now),

- there was some lambda uses that can now be avoided.

That first cleanup already simplifies things a lot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
83
83
    )
84
84
from bzrlib.osutils import (
85
85
    format_date,
 
86
    format_date_with_offset_in_original_timezone,
86
87
    get_terminal_encoding,
87
88
    re_compile_checked,
88
89
    terminal_width,
89
90
    )
 
91
from bzrlib.symbol_versioning import (
 
92
    deprecated_function,
 
93
    deprecated_in,
 
94
    )
90
95
 
91
96
 
92
97
def find_touching_revisions(branch, file_id):
303
308
 
304
309
 
305
310
class Logger(object):
306
 
    """An object the generates, formats and displays a log."""
 
311
    """An object that generates, formats and displays a log."""
307
312
 
308
313
    def __init__(self, branch, rqst):
309
314
        """Create a Logger.
384
389
        :return: An iterator yielding LogRevision objects.
385
390
        """
386
391
        rqst = self.rqst
 
392
        levels = rqst.get('levels')
 
393
        limit = rqst.get('limit')
 
394
        diff_type = rqst.get('diff_type')
387
395
        log_count = 0
388
396
        revision_iterator = self._create_log_revision_iterator()
389
397
        for revs in revision_iterator:
390
398
            for (rev_id, revno, merge_depth), rev, delta in revs:
391
399
                # 0 levels means show everything; merge_depth counts from 0
392
 
                levels = rqst.get('levels')
393
400
                if levels != 0 and merge_depth >= levels:
394
401
                    continue
395
 
                diff = self._format_diff(rev, rev_id)
 
402
                if diff_type is None:
 
403
                    diff = None
 
404
                else:
 
405
                    diff = self._format_diff(rev, rev_id, diff_type)
396
406
                yield LogRevision(rev, revno, merge_depth, delta,
397
407
                    self.rev_tag_dict.get(rev_id), diff)
398
 
                limit = rqst.get('limit')
399
408
                if limit:
400
409
                    log_count += 1
401
410
                    if log_count >= limit:
402
411
                        return
403
412
 
404
 
    def _format_diff(self, rev, rev_id):
405
 
        diff_type = self.rqst.get('diff_type')
406
 
        if diff_type is None:
407
 
            return None
 
413
    def _format_diff(self, rev, rev_id, diff_type):
408
414
        repo = self.branch.repository
409
415
        if len(rev.parent_ids) == 0:
410
416
            ancestor_id = _mod_revision.NULL_REVISION
595
601
        else:
596
602
            # not obvious
597
603
            return False
 
604
    # if either start or end is not specified then we use either the first or
 
605
    # the last revision and *they* are obvious ancestors.
598
606
    return True
599
607
 
600
608
 
662
670
                depth_adjustment = merge_depth
663
671
            if depth_adjustment:
664
672
                if merge_depth < depth_adjustment:
 
673
                    # From now on we reduce the depth adjustement, this can be
 
674
                    # surprising for users. The alternative requires two passes
 
675
                    # which breaks the fast display of the first revision
 
676
                    # though.
665
677
                    depth_adjustment = merge_depth
666
678
                merge_depth -= depth_adjustment
667
679
            yield rev_id, '.'.join(map(str, revno)), merge_depth
668
680
 
669
681
 
 
682
@deprecated_function(deprecated_in((2, 2, 0)))
670
683
def calculate_view_revisions(branch, start_revision, end_revision, direction,
671
684
        specific_fileid, generate_merge_revisions):
672
685
    """Calculate the revisions to view.
674
687
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
675
688
             a list of the same tuples.
676
689
    """
677
 
    # This method is no longer called by the main code path.
678
 
    # It is retained for API compatibility and may be deprecated
679
 
    # soon. IGC 20090116
680
690
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
681
691
        end_revision)
682
692
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
1032
1042
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1033
1043
 
1034
1044
 
 
1045
@deprecated_function(deprecated_in((2, 2, 0)))
1035
1046
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
1036
1047
    """Filter view_revisions based on revision ranges.
1037
1048
 
1046
1057
 
1047
1058
    :return: The filtered view_revisions.
1048
1059
    """
1049
 
    # This method is no longer called by the main code path.
1050
 
    # It may be removed soon. IGC 20090127
1051
1060
    if start_rev_id or end_rev_id:
1052
1061
        revision_ids = [r for r, n, d in view_revisions]
1053
1062
        if start_rev_id:
1159
1168
    return result
1160
1169
 
1161
1170
 
 
1171
@deprecated_function(deprecated_in((2, 2, 0)))
1162
1172
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
1163
1173
                       include_merges=True):
1164
1174
    """Produce an iterator of revisions to show
1165
1175
    :return: an iterator of (revision_id, revno, merge_depth)
1166
1176
    (if there is no revno for a revision, None is supplied)
1167
1177
    """
1168
 
    # This method is no longer called by the main code path.
1169
 
    # It is retained for API compatibility and may be deprecated
1170
 
    # soon. IGC 20090127
1171
1178
    if not include_merges:
1172
1179
        revision_ids = mainline_revs[1:]
1173
1180
        if direction == 'reverse':
1291
1298
    preferred_levels = 0
1292
1299
 
1293
1300
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1294
 
                 delta_format=None, levels=None, show_advice=False):
 
1301
                 delta_format=None, levels=None, show_advice=False,
 
1302
                 to_exact_file=None):
1295
1303
        """Create a LogFormatter.
1296
1304
 
1297
1305
        :param to_file: the file to output to
 
1306
        :param to_exact_file: if set, gives an output stream to which 
 
1307
             non-Unicode diffs are written.
1298
1308
        :param show_ids: if True, revision-ids are to be displayed
1299
1309
        :param show_timezone: the timezone to use
1300
1310
        :param delta_format: the level of delta information to display
1307
1317
        self.to_file = to_file
1308
1318
        # 'exact' stream used to show diff, it should print content 'as is'
1309
1319
        # and should not try to decode/encode it to unicode to avoid bug #328007
1310
 
        self.to_exact_file = getattr(to_file, 'stream', to_file)
 
1320
        if to_exact_file is not None:
 
1321
            self.to_exact_file = to_exact_file
 
1322
        else:
 
1323
            # XXX: somewhat hacky; this assumes it's a codec writer; it's better
 
1324
            # for code that expects to get diffs to pass in the exact file
 
1325
            # stream
 
1326
            self.to_exact_file = getattr(to_file, 'stream', to_file)
1311
1327
        self.show_ids = show_ids
1312
1328
        self.show_timezone = show_timezone
1313
1329
        if delta_format is None:
1367
1383
        else:
1368
1384
            return ''
1369
1385
 
1370
 
    def show_foreign_info(self, rev, indent):
 
1386
    def show_properties(self, revision, indent):
 
1387
        """Displays the custom properties returned by each registered handler.
 
1388
 
 
1389
        If a registered handler raises an error it is propagated.
 
1390
        """
 
1391
        for line in self.custom_properties(revision):
 
1392
            self.to_file.write("%s%s\n" % (indent, line))
 
1393
 
 
1394
    def custom_properties(self, revision):
 
1395
        """Format the custom properties returned by each registered handler.
 
1396
 
 
1397
        If a registered handler raises an error it is propagated.
 
1398
 
 
1399
        :return: a list of formatted lines (excluding trailing newlines)
 
1400
        """
 
1401
        lines = self._foreign_info_properties(revision)
 
1402
        for key, handler in properties_handler_registry.iteritems():
 
1403
            lines.extend(self._format_properties(handler(revision)))
 
1404
        return lines
 
1405
 
 
1406
    def _foreign_info_properties(self, rev):
1371
1407
        """Custom log displayer for foreign revision identifiers.
1372
1408
 
1373
1409
        :param rev: Revision object.
1374
1410
        """
1375
1411
        # Revision comes directly from a foreign repository
1376
1412
        if isinstance(rev, foreign.ForeignRevision):
1377
 
            self._write_properties(indent, rev.mapping.vcs.show_foreign_revid(
1378
 
                rev.foreign_revid))
1379
 
            return
 
1413
            return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
1380
1414
 
1381
1415
        # Imported foreign revision revision ids always contain :
1382
1416
        if not ":" in rev.revision_id:
1383
 
            return
 
1417
            return []
1384
1418
 
1385
1419
        # Revision was once imported from a foreign repository
1386
1420
        try:
1387
1421
            foreign_revid, mapping = \
1388
1422
                foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1389
1423
        except errors.InvalidRevisionId:
1390
 
            return
 
1424
            return []
1391
1425
 
1392
 
        self._write_properties(indent, 
 
1426
        return self._format_properties(
1393
1427
            mapping.vcs.show_foreign_revid(foreign_revid))
1394
1428
 
1395
 
    def show_properties(self, revision, indent):
1396
 
        """Displays the custom properties returned by each registered handler.
1397
 
 
1398
 
        If a registered handler raises an error it is propagated.
1399
 
        """
1400
 
        for key, handler in properties_handler_registry.iteritems():
1401
 
            self._write_properties(indent, handler(revision))
1402
 
 
1403
 
    def _write_properties(self, indent, properties):
 
1429
    def _format_properties(self, properties):
 
1430
        lines = []
1404
1431
        for key, value in properties.items():
1405
 
            self.to_file.write(indent + key + ': ' + value + '\n')
 
1432
            lines.append(key + ': ' + value)
 
1433
        return lines
1406
1434
 
1407
1435
    def show_diff(self, to_file, diff, indent):
1408
1436
        for l in diff.rstrip().split('\n'):
1409
1437
            to_file.write(indent + '%s\n' % (l,))
1410
1438
 
1411
1439
 
 
1440
# Separator between revisions in long format
 
1441
_LONG_SEP = '-' * 60
 
1442
 
 
1443
 
1412
1444
class LongLogFormatter(LogFormatter):
1413
1445
 
1414
1446
    supports_merge_revisions = True
1417
1449
    supports_tags = True
1418
1450
    supports_diff = True
1419
1451
 
 
1452
    def __init__(self, *args, **kwargs):
 
1453
        super(LongLogFormatter, self).__init__(*args, **kwargs)
 
1454
        if self.show_timezone == 'original':
 
1455
            self.date_string = self._date_string_original_timezone
 
1456
        else:
 
1457
            self.date_string = self._date_string_with_timezone
 
1458
 
 
1459
    def _date_string_with_timezone(self, rev):
 
1460
        return format_date(rev.timestamp, rev.timezone or 0,
 
1461
                           self.show_timezone)
 
1462
 
 
1463
    def _date_string_original_timezone(self, rev):
 
1464
        return format_date_with_offset_in_original_timezone(rev.timestamp,
 
1465
            rev.timezone or 0)
 
1466
 
1420
1467
    def log_revision(self, revision):
1421
1468
        """Log a revision, either merged or not."""
1422
1469
        indent = '    ' * revision.merge_depth
1423
 
        to_file = self.to_file
1424
 
        to_file.write(indent + '-' * 60 + '\n')
 
1470
        lines = [_LONG_SEP]
1425
1471
        if revision.revno is not None:
1426
 
            to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
 
1472
            lines.append('revno: %s%s' % (revision.revno,
1427
1473
                self.merge_marker(revision)))
1428
1474
        if revision.tags:
1429
 
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
 
1475
            lines.append('tags: %s' % (', '.join(revision.tags)))
1430
1476
        if self.show_ids:
1431
 
            to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
1432
 
            to_file.write('\n')
 
1477
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
1433
1478
            for parent_id in revision.rev.parent_ids:
1434
 
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
1435
 
        self.show_foreign_info(revision.rev, indent)
1436
 
        self.show_properties(revision.rev, indent)
 
1479
                lines.append('parent: %s' % (parent_id,))
 
1480
        lines.extend(self.custom_properties(revision.rev))
1437
1481
 
1438
1482
        committer = revision.rev.committer
1439
1483
        authors = revision.rev.get_apparent_authors()
1440
1484
        if authors != [committer]:
1441
 
            to_file.write(indent + 'author: %s\n' % (", ".join(authors),))
1442
 
        to_file.write(indent + 'committer: %s\n' % (committer,))
 
1485
            lines.append('author: %s' % (", ".join(authors),))
 
1486
        lines.append('committer: %s' % (committer,))
1443
1487
 
1444
1488
        branch_nick = revision.rev.properties.get('branch-nick', None)
1445
1489
        if branch_nick is not None:
1446
 
            to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))
1447
 
 
1448
 
        date_str = format_date(revision.rev.timestamp,
1449
 
                               revision.rev.timezone or 0,
1450
 
                               self.show_timezone)
1451
 
        to_file.write(indent + 'timestamp: %s\n' % (date_str,))
1452
 
 
1453
 
        to_file.write(indent + 'message:\n')
 
1490
            lines.append('branch nick: %s' % (branch_nick,))
 
1491
 
 
1492
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
 
1493
 
 
1494
        lines.append('message:')
1454
1495
        if not revision.rev.message:
1455
 
            to_file.write(indent + '  (no message)\n')
 
1496
            lines.append('  (no message)')
1456
1497
        else:
1457
1498
            message = revision.rev.message.rstrip('\r\n')
1458
1499
            for l in message.split('\n'):
1459
 
                to_file.write(indent + '  %s\n' % (l,))
 
1500
                lines.append('  %s' % (l,))
 
1501
 
 
1502
        # Dump the output, appending the delta and diff if requested
 
1503
        to_file = self.to_file
 
1504
        to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
1460
1505
        if revision.delta is not None:
1461
1506
            # We don't respect delta_format for compatibility
1462
1507
            revision.delta.show(to_file, self.show_ids, indent=indent,
1463
1508
                                short_status=False)
1464
1509
        if revision.diff is not None:
1465
1510
            to_file.write(indent + 'diff:\n')
 
1511
            to_file.flush()
1466
1512
            # Note: we explicitly don't indent the diff (relative to the
1467
1513
            # revision information) so that the output can be fed to patch -p0
1468
1514
            self.show_diff(self.to_exact_file, revision.diff, indent)
 
1515
            self.to_exact_file.flush()
1469
1516
 
1470
1517
    def get_advice_separator(self):
1471
1518
        """Get the text separating the log from the closing advice."""
1515
1562
                            self.show_timezone, date_fmt="%Y-%m-%d",
1516
1563
                            show_offset=False),
1517
1564
                tags, self.merge_marker(revision)))
1518
 
        self.show_foreign_info(revision.rev, indent+offset)
1519
1565
        self.show_properties(revision.rev, indent+offset)
1520
1566
        if self.show_ids:
1521
1567
            to_file.write(indent + offset + 'revision-id:%s\n'
1543
1589
 
1544
1590
    def __init__(self, *args, **kwargs):
1545
1591
        super(LineLogFormatter, self).__init__(*args, **kwargs)
1546
 
        self._max_chars = terminal_width() - 1
 
1592
        width = terminal_width()
 
1593
        if width is not None:
 
1594
            # we need one extra space for terminals that wrap on last char
 
1595
            width = width - 1
 
1596
        self._max_chars = width
1547
1597
 
1548
1598
    def truncate(self, str, max_len):
1549
 
        if len(str) <= max_len:
 
1599
        if max_len is None or len(str) <= max_len:
1550
1600
            return str
1551
 
        return str[:max_len-3]+'...'
 
1601
        return str[:max_len-3] + '...'
1552
1602
 
1553
1603
    def date_string(self, rev):
1554
1604
        return format_date(rev.timestamp, rev.timezone or 0,
1846
1896
    :return: (branch, info_list, start_rev_info, end_rev_info) where
1847
1897
      info_list is a list of (relative_path, file_id, kind) tuples where
1848
1898
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
 
1899
      branch will be read-locked.
1849
1900
    """
1850
1901
    from builtins import _get_revision_range, safe_relpath_files
1851
1902
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
 
1903
    b.lock_read()
1852
1904
    # XXX: It's damn messy converting a list of paths to relative paths when
1853
1905
    # those paths might be deleted ones, they might be on a case-insensitive
1854
1906
    # filesystem and/or they might be in silly locations (like another branch).
1933
1985
 
1934
1986
properties_handler_registry = registry.Registry()
1935
1987
 
 
1988
# Use the properties handlers to print out bug information if available
 
1989
def _bugs_properties_handler(revision):
 
1990
    if revision.properties.has_key('bugs'):
 
1991
        bug_lines = revision.properties['bugs'].split('\n')
 
1992
        bug_rows = [line.split(' ', 1) for line in bug_lines]
 
1993
        fixed_bug_urls = [row[0] for row in bug_rows if
 
1994
                          len(row) > 1 and row[1] == 'fixed']
 
1995
        
 
1996
        if fixed_bug_urls:
 
1997
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
 
1998
    return {}
 
1999
 
 
2000
properties_handler_registry.register('bugs_properties_handler',
 
2001
                                     _bugs_properties_handler)
 
2002
 
1936
2003
 
1937
2004
# adapters which revision ids to log are filtered. When log is called, the
1938
2005
# log_rev_iterator is adapted through each of these factory methods.