~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-01-14 00:01:32 UTC
  • mfrom: (4957.1.1 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100114000132-3p3rabnonjw3gzqb
(jam) Merge bzr.stable, bringing in bug fixes #175839, #504390

Show diffs side-by-side

added added

removed removed

Lines of Context:
69
69
    config,
70
70
    diff,
71
71
    errors,
 
72
    foreign,
72
73
    repository as _mod_repository,
73
74
    revision as _mod_revision,
74
75
    revisionspec,
82
83
    )
83
84
from bzrlib.osutils import (
84
85
    format_date,
 
86
    format_date_with_offset_in_original_timezone,
85
87
    get_terminal_encoding,
86
88
    re_compile_checked,
87
89
    terminal_width,
383
385
        :return: An iterator yielding LogRevision objects.
384
386
        """
385
387
        rqst = self.rqst
 
388
        levels = rqst.get('levels')
 
389
        limit = rqst.get('limit')
 
390
        diff_type = rqst.get('diff_type')
386
391
        log_count = 0
387
392
        revision_iterator = self._create_log_revision_iterator()
388
393
        for revs in revision_iterator:
389
394
            for (rev_id, revno, merge_depth), rev, delta in revs:
390
395
                # 0 levels means show everything; merge_depth counts from 0
391
 
                levels = rqst.get('levels')
392
396
                if levels != 0 and merge_depth >= levels:
393
397
                    continue
394
 
                diff = self._format_diff(rev, rev_id)
 
398
                if diff_type is None:
 
399
                    diff = None
 
400
                else:
 
401
                    diff = self._format_diff(rev, rev_id, diff_type)
395
402
                yield LogRevision(rev, revno, merge_depth, delta,
396
403
                    self.rev_tag_dict.get(rev_id), diff)
397
 
                limit = rqst.get('limit')
398
404
                if limit:
399
405
                    log_count += 1
400
406
                    if log_count >= limit:
401
407
                        return
402
408
 
403
 
    def _format_diff(self, rev, rev_id):
404
 
        diff_type = self.rqst.get('diff_type')
405
 
        if diff_type is None:
406
 
            return None
 
409
    def _format_diff(self, rev, rev_id, diff_type):
407
410
        repo = self.branch.repository
408
411
        if len(rev.parent_ids) == 0:
409
412
            ancestor_id = _mod_revision.NULL_REVISION
1290
1293
    preferred_levels = 0
1291
1294
 
1292
1295
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1293
 
                 delta_format=None, levels=None, show_advice=False):
 
1296
            delta_format=None, levels=None, show_advice=False,
 
1297
            to_exact_file=None):
1294
1298
        """Create a LogFormatter.
1295
1299
 
1296
1300
        :param to_file: the file to output to
 
1301
        :param to_exact_file: if set, gives an output stream to which 
 
1302
             non-Unicode diffs are written.
1297
1303
        :param show_ids: if True, revision-ids are to be displayed
1298
1304
        :param show_timezone: the timezone to use
1299
1305
        :param delta_format: the level of delta information to display
1306
1312
        self.to_file = to_file
1307
1313
        # 'exact' stream used to show diff, it should print content 'as is'
1308
1314
        # and should not try to decode/encode it to unicode to avoid bug #328007
1309
 
        self.to_exact_file = getattr(to_file, 'stream', to_file)
 
1315
        if to_exact_file is not None:
 
1316
            self.to_exact_file = to_exact_file
 
1317
        else:
 
1318
            # XXX: somewhat hacky; this assumes it's a codec writer; it's better
 
1319
            # for code that expects to get diffs to pass in the exact file
 
1320
            # stream
 
1321
            self.to_exact_file = getattr(to_file, 'stream', to_file)
1310
1322
        self.show_ids = show_ids
1311
1323
        self.show_timezone = show_timezone
1312
1324
        if delta_format is None:
1371
1383
 
1372
1384
        If a registered handler raises an error it is propagated.
1373
1385
        """
 
1386
        for line in self.custom_properties(revision):
 
1387
            self.to_file.write("%s%s\n" % (indent, line))
 
1388
 
 
1389
    def custom_properties(self, revision):
 
1390
        """Format the custom properties returned by each registered handler.
 
1391
 
 
1392
        If a registered handler raises an error it is propagated.
 
1393
 
 
1394
        :return: a list of formatted lines (excluding trailing newlines)
 
1395
        """
 
1396
        lines = self._foreign_info_properties(revision)
1374
1397
        for key, handler in properties_handler_registry.iteritems():
1375
 
            for key, value in handler(revision).items():
1376
 
                self.to_file.write(indent + key + ': ' + value + '\n')
 
1398
            lines.extend(self._format_properties(handler(revision)))
 
1399
        return lines
 
1400
 
 
1401
    def _foreign_info_properties(self, rev):
 
1402
        """Custom log displayer for foreign revision identifiers.
 
1403
 
 
1404
        :param rev: Revision object.
 
1405
        """
 
1406
        # Revision comes directly from a foreign repository
 
1407
        if isinstance(rev, foreign.ForeignRevision):
 
1408
            return rev.mapping.vcs.show_foreign_revid(rev.foreign_revid)
 
1409
 
 
1410
        # Imported foreign revision revision ids always contain :
 
1411
        if not ":" in rev.revision_id:
 
1412
            return []
 
1413
 
 
1414
        # Revision was once imported from a foreign repository
 
1415
        try:
 
1416
            foreign_revid, mapping = \
 
1417
                foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
 
1418
        except errors.InvalidRevisionId:
 
1419
            return []
 
1420
 
 
1421
        return self._format_properties(
 
1422
            mapping.vcs.show_foreign_revid(foreign_revid))
 
1423
 
 
1424
    def _format_properties(self, properties):
 
1425
        lines = []
 
1426
        for key, value in properties.items():
 
1427
            lines.append(key + ': ' + value)
 
1428
        return lines
1377
1429
 
1378
1430
    def show_diff(self, to_file, diff, indent):
1379
1431
        for l in diff.rstrip().split('\n'):
1380
1432
            to_file.write(indent + '%s\n' % (l,))
1381
1433
 
1382
1434
 
 
1435
# Separator between revisions in long format
 
1436
_LONG_SEP = '-' * 60
 
1437
 
 
1438
 
1383
1439
class LongLogFormatter(LogFormatter):
1384
1440
 
1385
1441
    supports_merge_revisions = True
1388
1444
    supports_tags = True
1389
1445
    supports_diff = True
1390
1446
 
 
1447
    def __init__(self, *args, **kwargs):
 
1448
        super(LongLogFormatter, self).__init__(*args, **kwargs)
 
1449
        if self.show_timezone == 'original':
 
1450
            self.date_string = self._date_string_original_timezone
 
1451
        else:
 
1452
            self.date_string = self._date_string_with_timezone
 
1453
 
 
1454
    def _date_string_with_timezone(self, rev):
 
1455
        return format_date(rev.timestamp, rev.timezone or 0,
 
1456
                           self.show_timezone)
 
1457
 
 
1458
    def _date_string_original_timezone(self, rev):
 
1459
        return format_date_with_offset_in_original_timezone(rev.timestamp,
 
1460
            rev.timezone or 0)
 
1461
 
1391
1462
    def log_revision(self, revision):
1392
1463
        """Log a revision, either merged or not."""
1393
1464
        indent = '    ' * revision.merge_depth
1394
 
        to_file = self.to_file
1395
 
        to_file.write(indent + '-' * 60 + '\n')
 
1465
        lines = [_LONG_SEP]
1396
1466
        if revision.revno is not None:
1397
 
            to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
 
1467
            lines.append('revno: %s%s' % (revision.revno,
1398
1468
                self.merge_marker(revision)))
1399
1469
        if revision.tags:
1400
 
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
 
1470
            lines.append('tags: %s' % (', '.join(revision.tags)))
1401
1471
        if self.show_ids:
1402
 
            to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
1403
 
            to_file.write('\n')
 
1472
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
1404
1473
            for parent_id in revision.rev.parent_ids:
1405
 
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
1406
 
        self.show_properties(revision.rev, indent)
 
1474
                lines.append('parent: %s' % (parent_id,))
 
1475
        lines.extend(self.custom_properties(revision.rev))
1407
1476
 
1408
1477
        committer = revision.rev.committer
1409
1478
        authors = revision.rev.get_apparent_authors()
1410
1479
        if authors != [committer]:
1411
 
            to_file.write(indent + 'author: %s\n' % (", ".join(authors),))
1412
 
        to_file.write(indent + 'committer: %s\n' % (committer,))
 
1480
            lines.append('author: %s' % (", ".join(authors),))
 
1481
        lines.append('committer: %s' % (committer,))
1413
1482
 
1414
1483
        branch_nick = revision.rev.properties.get('branch-nick', None)
1415
1484
        if branch_nick is not None:
1416
 
            to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))
1417
 
 
1418
 
        date_str = format_date(revision.rev.timestamp,
1419
 
                               revision.rev.timezone or 0,
1420
 
                               self.show_timezone)
1421
 
        to_file.write(indent + 'timestamp: %s\n' % (date_str,))
1422
 
 
1423
 
        to_file.write(indent + 'message:\n')
 
1485
            lines.append('branch nick: %s' % (branch_nick,))
 
1486
 
 
1487
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
 
1488
 
 
1489
        lines.append('message:')
1424
1490
        if not revision.rev.message:
1425
 
            to_file.write(indent + '  (no message)\n')
 
1491
            lines.append('  (no message)')
1426
1492
        else:
1427
1493
            message = revision.rev.message.rstrip('\r\n')
1428
1494
            for l in message.split('\n'):
1429
 
                to_file.write(indent + '  %s\n' % (l,))
 
1495
                lines.append('  %s' % (l,))
 
1496
 
 
1497
        # Dump the output, appending the delta and diff if requested
 
1498
        to_file = self.to_file
 
1499
        to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
1430
1500
        if revision.delta is not None:
1431
1501
            # We don't respect delta_format for compatibility
1432
1502
            revision.delta.show(to_file, self.show_ids, indent=indent,
1433
1503
                                short_status=False)
1434
1504
        if revision.diff is not None:
1435
1505
            to_file.write(indent + 'diff:\n')
 
1506
            to_file.flush()
1436
1507
            # Note: we explicitly don't indent the diff (relative to the
1437
1508
            # revision information) so that the output can be fed to patch -p0
1438
1509
            self.show_diff(self.to_exact_file, revision.diff, indent)
 
1510
            self.to_exact_file.flush()
1439
1511
 
1440
1512
    def get_advice_separator(self):
1441
1513
        """Get the text separating the log from the closing advice."""
1512
1584
 
1513
1585
    def __init__(self, *args, **kwargs):
1514
1586
        super(LineLogFormatter, self).__init__(*args, **kwargs)
1515
 
        self._max_chars = terminal_width() - 1
 
1587
        width = terminal_width()
 
1588
        if width is not None:
 
1589
            # we need one extra space for terminals that wrap on last char
 
1590
            width = width - 1
 
1591
        self._max_chars = width
1516
1592
 
1517
1593
    def truncate(self, str, max_len):
1518
 
        if len(str) <= max_len:
 
1594
        if max_len is None or len(str) <= max_len:
1519
1595
            return str
1520
 
        return str[:max_len-3]+'...'
 
1596
        return str[:max_len-3] + '...'
1521
1597
 
1522
1598
    def date_string(self, rev):
1523
1599
        return format_date(rev.timestamp, rev.timezone or 0,
1815
1891
    :return: (branch, info_list, start_rev_info, end_rev_info) where
1816
1892
      info_list is a list of (relative_path, file_id, kind) tuples where
1817
1893
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
 
1894
      branch will be read-locked.
1818
1895
    """
1819
1896
    from builtins import _get_revision_range, safe_relpath_files
1820
1897
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
 
1898
    b.lock_read()
1821
1899
    # XXX: It's damn messy converting a list of paths to relative paths when
1822
1900
    # those paths might be deleted ones, they might be on a case-insensitive
1823
1901
    # filesystem and/or they might be in silly locations (like another branch).
1901
1979
 
1902
1980
 
1903
1981
properties_handler_registry = registry.Registry()
1904
 
properties_handler_registry.register_lazy("foreign",
1905
 
                                          "bzrlib.foreign",
1906
 
                                          "show_foreign_properties")
 
1982
 
 
1983
# Use the properties handlers to print out bug information if available
 
1984
def _bugs_properties_handler(revision):
 
1985
    if revision.properties.has_key('bugs'):
 
1986
        bug_lines = revision.properties['bugs'].split('\n')
 
1987
        bug_rows = [line.split(' ', 1) for line in bug_lines]
 
1988
        fixed_bug_urls = [row[0] for row in bug_rows if
 
1989
                          len(row) > 1 and row[1] == 'fixed']
 
1990
        
 
1991
        if fixed_bug_urls:
 
1992
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
 
1993
    return {}
 
1994
 
 
1995
properties_handler_registry.register('bugs_properties_handler',
 
1996
                                     _bugs_properties_handler)
1907
1997
 
1908
1998
 
1909
1999
# adapters which revision ids to log are filtered. When log is called, the