/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: John Arbash Meinel
  • Date: 2009-04-21 23:54:16 UTC
  • mto: (4300.1.7 groupcompress_info)
  • mto: This revision was merged to the branch mainline in revision 4301.
  • Revision ID: john@arbash-meinel.com-20090421235416-f0cz6ilf5cufbugi
Fix bug #364900, properly remove the 64kB that was just encoded in the copy.
Also, stop supporting None as a copy length in 'encode_copy_instruction'.
It was only used by the test suite, and it is good to pull that sort of thing out of
production code. (Besides, setting the copy to 64kB has the same effect.)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2009 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
69
69
    config,
70
70
    diff,
71
71
    errors,
72
 
    foreign,
73
72
    repository as _mod_repository,
74
73
    revision as _mod_revision,
75
74
    revisionspec,
83
82
    )
84
83
from bzrlib.osutils import (
85
84
    format_date,
86
 
    format_date_with_offset_in_original_timezone,
87
85
    get_terminal_encoding,
88
86
    re_compile_checked,
89
87
    terminal_width,
90
88
    )
91
 
from bzrlib.symbol_versioning import (
92
 
    deprecated_function,
93
 
    deprecated_in,
94
 
    )
95
89
 
96
90
 
97
91
def find_touching_revisions(branch, file_id):
109
103
    last_path = None
110
104
    revno = 1
111
105
    for revision_id in branch.revision_history():
112
 
        this_inv = branch.repository.get_inventory(revision_id)
 
106
        this_inv = branch.repository.get_revision_inventory(revision_id)
113
107
        if file_id in this_inv:
114
108
            this_ie = this_inv[file_id]
115
109
            this_path = this_inv.id2path(file_id)
220
214
    'direction': 'reverse',
221
215
    'levels': 1,
222
216
    'generate_tags': True,
223
 
    'exclude_common_ancestry': False,
224
217
    '_match_using_deltas': True,
225
218
    }
226
219
 
227
220
 
228
221
def make_log_request_dict(direction='reverse', specific_fileids=None,
229
 
                          start_revision=None, end_revision=None, limit=None,
230
 
                          message_search=None, levels=1, generate_tags=True,
231
 
                          delta_type=None,
232
 
                          diff_type=None, _match_using_deltas=True,
233
 
                          exclude_common_ancestry=False,
234
 
                          ):
 
222
    start_revision=None, end_revision=None, limit=None,
 
223
    message_search=None, levels=1, generate_tags=True, delta_type=None,
 
224
    diff_type=None, _match_using_deltas=True):
235
225
    """Convenience function for making a logging request dictionary.
236
226
 
237
227
    Using this function may make code slightly safer by ensuring
275
265
      algorithm used for matching specific_fileids. This parameter
276
266
      may be removed in the future so bzrlib client code should NOT
277
267
      use it.
278
 
 
279
 
    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
280
 
      range operator or as a graph difference.
281
268
    """
282
269
    return {
283
270
        'direction': direction,
290
277
        'generate_tags': generate_tags,
291
278
        'delta_type': delta_type,
292
279
        'diff_type': diff_type,
293
 
        'exclude_common_ancestry': exclude_common_ancestry,
294
280
        # Add 'private' attributes for features that may be deprecated
295
281
        '_match_using_deltas': _match_using_deltas,
296
282
    }
316
302
 
317
303
 
318
304
class Logger(object):
319
 
    """An object that generates, formats and displays a log."""
 
305
    """An object the generates, formats and displays a log."""
320
306
 
321
307
    def __init__(self, branch, rqst):
322
308
        """Create a Logger.
397
383
        :return: An iterator yielding LogRevision objects.
398
384
        """
399
385
        rqst = self.rqst
400
 
        levels = rqst.get('levels')
401
 
        limit = rqst.get('limit')
402
 
        diff_type = rqst.get('diff_type')
403
386
        log_count = 0
404
387
        revision_iterator = self._create_log_revision_iterator()
405
388
        for revs in revision_iterator:
406
389
            for (rev_id, revno, merge_depth), rev, delta in revs:
407
390
                # 0 levels means show everything; merge_depth counts from 0
 
391
                levels = rqst.get('levels')
408
392
                if levels != 0 and merge_depth >= levels:
409
393
                    continue
410
 
                if diff_type is None:
411
 
                    diff = None
412
 
                else:
413
 
                    diff = self._format_diff(rev, rev_id, diff_type)
 
394
                diff = self._format_diff(rev, rev_id)
414
395
                yield LogRevision(rev, revno, merge_depth, delta,
415
396
                    self.rev_tag_dict.get(rev_id), diff)
 
397
                limit = rqst.get('limit')
416
398
                if limit:
417
399
                    log_count += 1
418
400
                    if log_count >= limit:
419
401
                        return
420
402
 
421
 
    def _format_diff(self, rev, rev_id, diff_type):
 
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
422
407
        repo = self.branch.repository
423
408
        if len(rev.parent_ids) == 0:
424
409
            ancestor_id = _mod_revision.NULL_REVISION
463
448
        generate_merge_revisions = rqst.get('levels') != 1
464
449
        delayed_graph_generation = not rqst.get('specific_fileids') and (
465
450
                rqst.get('limit') or self.start_rev_id or self.end_rev_id)
466
 
        view_revisions = _calc_view_revisions(
467
 
            self.branch, self.start_rev_id, self.end_rev_id,
468
 
            rqst.get('direction'),
469
 
            generate_merge_revisions=generate_merge_revisions,
470
 
            delayed_graph_generation=delayed_graph_generation,
471
 
            exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
 
451
        view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
 
452
            self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
 
453
            delayed_graph_generation=delayed_graph_generation)
472
454
 
473
455
        # Apply the other filters
474
456
        return make_log_rev_iterator(self.branch, view_revisions,
481
463
        # Note that we always generate the merge revisions because
482
464
        # filter_revisions_touching_file_id() requires them ...
483
465
        rqst = self.rqst
484
 
        view_revisions = _calc_view_revisions(
485
 
            self.branch, self.start_rev_id, self.end_rev_id,
486
 
            rqst.get('direction'), generate_merge_revisions=True,
487
 
            exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
 
466
        view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
 
467
            self.end_rev_id, rqst.get('direction'), True)
488
468
        if not isinstance(view_revisions, list):
489
469
            view_revisions = list(view_revisions)
490
470
        view_revisions = _filter_revisions_touching_file_id(self.branch,
495
475
 
496
476
 
497
477
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
498
 
                         generate_merge_revisions,
499
 
                         delayed_graph_generation=False,
500
 
                         exclude_common_ancestry=False,
501
 
                         ):
 
478
    generate_merge_revisions, delayed_graph_generation=False):
502
479
    """Calculate the revisions to view.
503
480
 
504
481
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
505
482
             a list of the same tuples.
506
483
    """
507
 
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
508
 
        raise errors.BzrCommandError(
509
 
            '--exclude-common-ancestry requires two different revisions')
510
 
    if direction not in ('reverse', 'forward'):
511
 
        raise ValueError('invalid direction %r' % direction)
512
484
    br_revno, br_rev_id = branch.last_revision_info()
513
485
    if br_revno == 0:
514
486
        return []
515
487
 
516
 
    if (end_rev_id and start_rev_id == end_rev_id
517
 
        and (not generate_merge_revisions
518
 
             or not _has_merges(branch, end_rev_id))):
519
 
        # If a single revision is requested, check we can handle it
520
 
        iter_revs = _generate_one_revision(branch, end_rev_id, br_rev_id,
521
 
                                           br_revno)
522
 
    elif not generate_merge_revisions:
523
 
        # If we only want to see linear revisions, we can iterate ...
524
 
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
525
 
                                             direction)
526
 
        if direction == 'forward':
527
 
            iter_revs = reversed(iter_revs)
 
488
    # If a single revision is requested, check we can handle it
 
489
    generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
 
490
        (not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
 
491
    if generate_single_revision:
 
492
        return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
 
493
 
 
494
    # If we only want to see linear revisions, we can iterate ...
 
495
    if not generate_merge_revisions:
 
496
        return _generate_flat_revisions(branch, start_rev_id, end_rev_id,
 
497
            direction)
528
498
    else:
529
 
        iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
530
 
                                            direction, delayed_graph_generation,
531
 
                                            exclude_common_ancestry)
532
 
        if direction == 'forward':
533
 
            iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
534
 
    return iter_revs
 
499
        return _generate_all_revisions(branch, start_rev_id, end_rev_id,
 
500
            direction, delayed_graph_generation)
535
501
 
536
502
 
537
503
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
555
521
        except _StartNotLinearAncestor:
556
522
            raise errors.BzrCommandError('Start revision not found in'
557
523
                ' left-hand history of end revision.')
 
524
    if direction == 'forward':
 
525
        result = reversed(result)
558
526
    return result
559
527
 
560
528
 
561
529
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
562
 
                            delayed_graph_generation,
563
 
                            exclude_common_ancestry=False):
 
530
    delayed_graph_generation):
564
531
    # On large trees, generating the merge graph can take 30-60 seconds
565
532
    # so we delay doing it until a merge is detected, incrementally
566
533
    # returning initial (non-merge) revisions while we can.
567
 
 
568
 
    # The above is only true for old formats (<= 0.92), for newer formats, a
569
 
    # couple of seconds only should be needed to load the whole graph and the
570
 
    # other graph operations needed are even faster than that -- vila 100201
571
534
    initial_revisions = []
572
535
    if delayed_graph_generation:
573
536
        try:
574
 
            for rev_id, revno, depth in  _linear_view_revisions(
575
 
                branch, start_rev_id, end_rev_id):
 
537
            for rev_id, revno, depth in \
 
538
                _linear_view_revisions(branch, start_rev_id, end_rev_id):
576
539
                if _has_merges(branch, rev_id):
577
 
                    # The end_rev_id can be nested down somewhere. We need an
578
 
                    # explicit ancestry check. There is an ambiguity here as we
579
 
                    # may not raise _StartNotLinearAncestor for a revision that
580
 
                    # is an ancestor but not a *linear* one. But since we have
581
 
                    # loaded the graph to do the check (or calculate a dotted
582
 
                    # revno), we may as well accept to show the log...  We need
583
 
                    # the check only if start_rev_id is not None as all
584
 
                    # revisions have _mod_revision.NULL_REVISION as an ancestor
585
 
                    # -- vila 20100319
586
 
                    graph = branch.repository.get_graph()
587
 
                    if (start_rev_id is not None
588
 
                        and not graph.is_ancestor(start_rev_id, end_rev_id)):
589
 
                        raise _StartNotLinearAncestor()
590
 
                    # Since we collected the revisions so far, we need to
591
 
                    # adjust end_rev_id.
592
540
                    end_rev_id = rev_id
593
541
                    break
594
542
                else:
595
543
                    initial_revisions.append((rev_id, revno, depth))
596
544
            else:
597
545
                # No merged revisions found
598
 
                return initial_revisions
 
546
                if direction == 'reverse':
 
547
                    return initial_revisions
 
548
                elif direction == 'forward':
 
549
                    return reversed(initial_revisions)
 
550
                else:
 
551
                    raise ValueError('invalid direction %r' % direction)
599
552
        except _StartNotLinearAncestor:
600
553
            # A merge was never detected so the lower revision limit can't
601
554
            # be nested down somewhere
602
555
            raise errors.BzrCommandError('Start revision not found in'
603
556
                ' history of end revision.')
604
557
 
605
 
    # We exit the loop above because we encounter a revision with merges, from
606
 
    # this revision, we need to switch to _graph_view_revisions.
607
 
 
608
558
    # A log including nested merges is required. If the direction is reverse,
609
559
    # we rebase the initial merge depths so that the development line is
610
560
    # shown naturally, i.e. just like it is for linear logging. We can easily
612
562
    # indented at the end seems slightly nicer in that case.
613
563
    view_revisions = chain(iter(initial_revisions),
614
564
        _graph_view_revisions(branch, start_rev_id, end_rev_id,
615
 
                              rebase_initial_depths=(direction == 'reverse'),
616
 
                              exclude_common_ancestry=exclude_common_ancestry))
617
 
    return view_revisions
 
565
        rebase_initial_depths=direction == 'reverse'))
 
566
    if direction == 'reverse':
 
567
        return view_revisions
 
568
    elif direction == 'forward':
 
569
        # Forward means oldest first, adjusting for depth.
 
570
        view_revisions = reverse_by_depth(list(view_revisions))
 
571
        return _rebase_merge_depth(view_revisions)
 
572
    else:
 
573
        raise ValueError('invalid direction %r' % direction)
618
574
 
619
575
 
620
576
def _has_merges(branch, rev_id):
638
594
        else:
639
595
            # not obvious
640
596
            return False
641
 
    # if either start or end is not specified then we use either the first or
642
 
    # the last revision and *they* are obvious ancestors.
643
597
    return True
644
598
 
645
599
 
678
632
 
679
633
 
680
634
def _graph_view_revisions(branch, start_rev_id, end_rev_id,
681
 
                          rebase_initial_depths=True,
682
 
                          exclude_common_ancestry=False):
 
635
    rebase_initial_depths=True):
683
636
    """Calculate revisions to view including merges, newest to oldest.
684
637
 
685
638
    :param branch: the branch
689
642
      revision is found?
690
643
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
691
644
    """
692
 
    if exclude_common_ancestry:
693
 
        stop_rule = 'with-merges-without-common-ancestry'
694
 
    else:
695
 
        stop_rule = 'with-merges'
696
645
    view_revisions = branch.iter_merge_sorted_revisions(
697
646
        start_revision_id=end_rev_id, stop_revision_id=start_rev_id,
698
 
        stop_rule=stop_rule)
 
647
        stop_rule="with-merges")
699
648
    if not rebase_initial_depths:
700
649
        for (rev_id, merge_depth, revno, end_of_merge
701
650
             ) in view_revisions:
712
661
                depth_adjustment = merge_depth
713
662
            if depth_adjustment:
714
663
                if merge_depth < depth_adjustment:
715
 
                    # From now on we reduce the depth adjustement, this can be
716
 
                    # surprising for users. The alternative requires two passes
717
 
                    # which breaks the fast display of the first revision
718
 
                    # though.
719
664
                    depth_adjustment = merge_depth
720
665
                merge_depth -= depth_adjustment
721
666
            yield rev_id, '.'.join(map(str, revno)), merge_depth
722
667
 
723
668
 
724
 
@deprecated_function(deprecated_in((2, 2, 0)))
725
669
def calculate_view_revisions(branch, start_revision, end_revision, direction,
726
670
        specific_fileid, generate_merge_revisions):
727
671
    """Calculate the revisions to view.
729
673
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
730
674
             a list of the same tuples.
731
675
    """
 
676
    # This method is no longer called by the main code path.
 
677
    # It is retained for API compatibility and may be deprecated
 
678
    # soon. IGC 20090116
732
679
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
733
680
        end_revision)
734
681
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
1084
1031
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1085
1032
 
1086
1033
 
1087
 
@deprecated_function(deprecated_in((2, 2, 0)))
1088
1034
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
1089
1035
    """Filter view_revisions based on revision ranges.
1090
1036
 
1099
1045
 
1100
1046
    :return: The filtered view_revisions.
1101
1047
    """
 
1048
    # This method is no longer called by the main code path.
 
1049
    # It may be removed soon. IGC 20090127
1102
1050
    if start_rev_id or end_rev_id:
1103
1051
        revision_ids = [r for r, n, d in view_revisions]
1104
1052
        if start_rev_id:
1210
1158
    return result
1211
1159
 
1212
1160
 
1213
 
@deprecated_function(deprecated_in((2, 2, 0)))
1214
1161
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
1215
1162
                       include_merges=True):
1216
1163
    """Produce an iterator of revisions to show
1217
1164
    :return: an iterator of (revision_id, revno, merge_depth)
1218
1165
    (if there is no revno for a revision, None is supplied)
1219
1166
    """
 
1167
    # This method is no longer called by the main code path.
 
1168
    # It is retained for API compatibility and may be deprecated
 
1169
    # soon. IGC 20090127
1220
1170
    if not include_merges:
1221
1171
        revision_ids = mainline_revs[1:]
1222
1172
        if direction == 'reverse':
1340
1290
    preferred_levels = 0
1341
1291
 
1342
1292
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1343
 
                 delta_format=None, levels=None, show_advice=False,
1344
 
                 to_exact_file=None):
 
1293
                 delta_format=None, levels=None, show_advice=False):
1345
1294
        """Create a LogFormatter.
1346
1295
 
1347
1296
        :param to_file: the file to output to
1348
 
        :param to_exact_file: if set, gives an output stream to which 
1349
 
             non-Unicode diffs are written.
1350
1297
        :param show_ids: if True, revision-ids are to be displayed
1351
1298
        :param show_timezone: the timezone to use
1352
1299
        :param delta_format: the level of delta information to display
1359
1306
        self.to_file = to_file
1360
1307
        # 'exact' stream used to show diff, it should print content 'as is'
1361
1308
        # and should not try to decode/encode it to unicode to avoid bug #328007
1362
 
        if to_exact_file is not None:
1363
 
            self.to_exact_file = to_exact_file
1364
 
        else:
1365
 
            # XXX: somewhat hacky; this assumes it's a codec writer; it's better
1366
 
            # for code that expects to get diffs to pass in the exact file
1367
 
            # stream
1368
 
            self.to_exact_file = getattr(to_file, 'stream', to_file)
 
1309
        self.to_exact_file = getattr(to_file, 'stream', to_file)
1369
1310
        self.show_ids = show_ids
1370
1311
        self.show_timezone = show_timezone
1371
1312
        if delta_format is None:
1430
1371
 
1431
1372
        If a registered handler raises an error it is propagated.
1432
1373
        """
1433
 
        for line in self.custom_properties(revision):
1434
 
            self.to_file.write("%s%s\n" % (indent, line))
1435
 
 
1436
 
    def custom_properties(self, revision):
1437
 
        """Format the custom properties returned by each registered handler.
1438
 
 
1439
 
        If a registered handler raises an error it is propagated.
1440
 
 
1441
 
        :return: a list of formatted lines (excluding trailing newlines)
1442
 
        """
1443
 
        lines = self._foreign_info_properties(revision)
1444
1374
        for key, handler in properties_handler_registry.iteritems():
1445
 
            lines.extend(self._format_properties(handler(revision)))
1446
 
        return lines
1447
 
 
1448
 
    def _foreign_info_properties(self, rev):
1449
 
        """Custom log displayer for foreign revision identifiers.
1450
 
 
1451
 
        :param rev: Revision object.
1452
 
        """
1453
 
        # Revision comes directly from a foreign repository
1454
 
        if isinstance(rev, foreign.ForeignRevision):
1455
 
            return self._format_properties(
1456
 
                rev.mapping.vcs.show_foreign_revid(rev.foreign_revid))
1457
 
 
1458
 
        # Imported foreign revision revision ids always contain :
1459
 
        if not ":" in rev.revision_id:
1460
 
            return []
1461
 
 
1462
 
        # Revision was once imported from a foreign repository
1463
 
        try:
1464
 
            foreign_revid, mapping = \
1465
 
                foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1466
 
        except errors.InvalidRevisionId:
1467
 
            return []
1468
 
 
1469
 
        return self._format_properties(
1470
 
            mapping.vcs.show_foreign_revid(foreign_revid))
1471
 
 
1472
 
    def _format_properties(self, properties):
1473
 
        lines = []
1474
 
        for key, value in properties.items():
1475
 
            lines.append(key + ': ' + value)
1476
 
        return lines
 
1375
            for key, value in handler(revision).items():
 
1376
                self.to_file.write(indent + key + ': ' + value + '\n')
1477
1377
 
1478
1378
    def show_diff(self, to_file, diff, indent):
1479
1379
        for l in diff.rstrip().split('\n'):
1480
1380
            to_file.write(indent + '%s\n' % (l,))
1481
1381
 
1482
1382
 
1483
 
# Separator between revisions in long format
1484
 
_LONG_SEP = '-' * 60
1485
 
 
1486
 
 
1487
1383
class LongLogFormatter(LogFormatter):
1488
1384
 
1489
1385
    supports_merge_revisions = True
1492
1388
    supports_tags = True
1493
1389
    supports_diff = True
1494
1390
 
1495
 
    def __init__(self, *args, **kwargs):
1496
 
        super(LongLogFormatter, self).__init__(*args, **kwargs)
1497
 
        if self.show_timezone == 'original':
1498
 
            self.date_string = self._date_string_original_timezone
1499
 
        else:
1500
 
            self.date_string = self._date_string_with_timezone
1501
 
 
1502
 
    def _date_string_with_timezone(self, rev):
1503
 
        return format_date(rev.timestamp, rev.timezone or 0,
1504
 
                           self.show_timezone)
1505
 
 
1506
 
    def _date_string_original_timezone(self, rev):
1507
 
        return format_date_with_offset_in_original_timezone(rev.timestamp,
1508
 
            rev.timezone or 0)
1509
 
 
1510
1391
    def log_revision(self, revision):
1511
1392
        """Log a revision, either merged or not."""
1512
1393
        indent = '    ' * revision.merge_depth
1513
 
        lines = [_LONG_SEP]
 
1394
        to_file = self.to_file
 
1395
        to_file.write(indent + '-' * 60 + '\n')
1514
1396
        if revision.revno is not None:
1515
 
            lines.append('revno: %s%s' % (revision.revno,
 
1397
            to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
1516
1398
                self.merge_marker(revision)))
1517
1399
        if revision.tags:
1518
 
            lines.append('tags: %s' % (', '.join(revision.tags)))
 
1400
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
1519
1401
        if self.show_ids:
1520
 
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
 
1402
            to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
 
1403
            to_file.write('\n')
1521
1404
            for parent_id in revision.rev.parent_ids:
1522
 
                lines.append('parent: %s' % (parent_id,))
1523
 
        lines.extend(self.custom_properties(revision.rev))
 
1405
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
 
1406
        self.show_properties(revision.rev, indent)
1524
1407
 
1525
1408
        committer = revision.rev.committer
1526
1409
        authors = revision.rev.get_apparent_authors()
1527
1410
        if authors != [committer]:
1528
 
            lines.append('author: %s' % (", ".join(authors),))
1529
 
        lines.append('committer: %s' % (committer,))
 
1411
            to_file.write(indent + 'author: %s\n' % (", ".join(authors),))
 
1412
        to_file.write(indent + 'committer: %s\n' % (committer,))
1530
1413
 
1531
1414
        branch_nick = revision.rev.properties.get('branch-nick', None)
1532
1415
        if branch_nick is not None:
1533
 
            lines.append('branch nick: %s' % (branch_nick,))
1534
 
 
1535
 
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1536
 
 
1537
 
        lines.append('message:')
 
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')
1538
1424
        if not revision.rev.message:
1539
 
            lines.append('  (no message)')
 
1425
            to_file.write(indent + '  (no message)\n')
1540
1426
        else:
1541
1427
            message = revision.rev.message.rstrip('\r\n')
1542
1428
            for l in message.split('\n'):
1543
 
                lines.append('  %s' % (l,))
1544
 
 
1545
 
        # Dump the output, appending the delta and diff if requested
1546
 
        to_file = self.to_file
1547
 
        to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
 
1429
                to_file.write(indent + '  %s\n' % (l,))
1548
1430
        if revision.delta is not None:
1549
 
            # Use the standard status output to display changes
1550
 
            from bzrlib.delta import report_delta
1551
 
            report_delta(to_file, revision.delta, short_status=False, 
1552
 
                         show_ids=self.show_ids, indent=indent)
 
1431
            # We don't respect delta_format for compatibility
 
1432
            revision.delta.show(to_file, self.show_ids, indent=indent,
 
1433
                                short_status=False)
1553
1434
        if revision.diff is not None:
1554
1435
            to_file.write(indent + 'diff:\n')
1555
 
            to_file.flush()
1556
1436
            # Note: we explicitly don't indent the diff (relative to the
1557
1437
            # revision information) so that the output can be fed to patch -p0
1558
1438
            self.show_diff(self.to_exact_file, revision.diff, indent)
1559
 
            self.to_exact_file.flush()
1560
1439
 
1561
1440
    def get_advice_separator(self):
1562
1441
        """Get the text separating the log from the closing advice."""
1618
1497
                to_file.write(indent + offset + '%s\n' % (l,))
1619
1498
 
1620
1499
        if revision.delta is not None:
1621
 
            # Use the standard status output to display changes
1622
 
            from bzrlib.delta import report_delta
1623
 
            report_delta(to_file, revision.delta, 
1624
 
                         short_status=self.delta_format==1, 
1625
 
                         show_ids=self.show_ids, indent=indent + offset)
 
1500
            revision.delta.show(to_file, self.show_ids, indent=indent + offset,
 
1501
                                short_status=self.delta_format==1)
1626
1502
        if revision.diff is not None:
1627
1503
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1628
1504
        to_file.write('\n')
1636
1512
 
1637
1513
    def __init__(self, *args, **kwargs):
1638
1514
        super(LineLogFormatter, self).__init__(*args, **kwargs)
1639
 
        width = terminal_width()
1640
 
        if width is not None:
1641
 
            # we need one extra space for terminals that wrap on last char
1642
 
            width = width - 1
1643
 
        self._max_chars = width
 
1515
        self._max_chars = terminal_width() - 1
1644
1516
 
1645
1517
    def truncate(self, str, max_len):
1646
 
        if max_len is None or len(str) <= max_len:
 
1518
        if len(str) <= max_len:
1647
1519
            return str
1648
 
        return str[:max_len-3] + '...'
 
1520
        return str[:max_len-3]+'...'
1649
1521
 
1650
1522
    def date_string(self, rev):
1651
1523
        return format_date(rev.timestamp, rev.timezone or 0,
1703
1575
                               self.show_timezone,
1704
1576
                               date_fmt='%Y-%m-%d',
1705
1577
                               show_offset=False)
1706
 
        committer_str = revision.rev.get_apparent_authors()[0].replace (' <', '  <')
 
1578
        committer_str = revision.rev.committer.replace (' <', '  <')
1707
1579
        to_file.write('%s  %s\n\n' % (date_str,committer_str))
1708
1580
 
1709
1581
        if revision.delta is not None and revision.delta.has_changed():
1943
1815
    :return: (branch, info_list, start_rev_info, end_rev_info) where
1944
1816
      info_list is a list of (relative_path, file_id, kind) tuples where
1945
1817
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
1946
 
      branch will be read-locked.
1947
1818
    """
1948
1819
    from builtins import _get_revision_range, safe_relpath_files
1949
1820
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
1950
 
    b.lock_read()
1951
1821
    # XXX: It's damn messy converting a list of paths to relative paths when
1952
1822
    # those paths might be deleted ones, they might be on a case-insensitive
1953
1823
    # filesystem and/or they might be in silly locations (like another branch).
2031
1901
 
2032
1902
 
2033
1903
properties_handler_registry = registry.Registry()
2034
 
 
2035
 
# Use the properties handlers to print out bug information if available
2036
 
def _bugs_properties_handler(revision):
2037
 
    if revision.properties.has_key('bugs'):
2038
 
        bug_lines = revision.properties['bugs'].split('\n')
2039
 
        bug_rows = [line.split(' ', 1) for line in bug_lines]
2040
 
        fixed_bug_urls = [row[0] for row in bug_rows if
2041
 
                          len(row) > 1 and row[1] == 'fixed']
2042
 
 
2043
 
        if fixed_bug_urls:
2044
 
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
2045
 
    return {}
2046
 
 
2047
 
properties_handler_registry.register('bugs_properties_handler',
2048
 
                                     _bugs_properties_handler)
 
1904
properties_handler_registry.register_lazy("foreign",
 
1905
                                          "bzrlib.foreign",
 
1906
                                          "show_foreign_properties")
2049
1907
 
2050
1908
 
2051
1909
# adapters which revision ids to log are filtered. When log is called, the