~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

(jelmer) Fix bug #1010339,
 use encoding_type='exact' for bzr testament (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
 
 
18
 
17
19
"""Code to show logs of changes.
18
20
 
19
21
Various flavors of log can be produced:
47
49
all the changes since the previous revision that touched hello.c.
48
50
"""
49
51
 
50
 
from __future__ import absolute_import
51
 
 
52
52
import codecs
53
53
from cStringIO import StringIO
54
54
from itertools import (
65
65
lazy_import(globals(), """
66
66
 
67
67
from bzrlib import (
 
68
    bzrdir,
68
69
    config,
69
 
    controldir,
70
70
    diff,
71
71
    errors,
72
72
    foreign,
74
74
    revision as _mod_revision,
75
75
    revisionspec,
76
76
    tsort,
 
77
    i18n,
77
78
    )
78
 
from bzrlib.i18n import gettext, ngettext
79
79
""")
80
80
 
81
81
from bzrlib import (
89
89
    get_terminal_encoding,
90
90
    terminal_width,
91
91
    )
 
92
from bzrlib.symbol_versioning import (
 
93
    deprecated_function,
 
94
    deprecated_in,
 
95
    )
92
96
 
93
97
 
94
98
def find_touching_revisions(branch, file_id):
105
109
    last_ie = None
106
110
    last_path = None
107
111
    revno = 1
108
 
    graph = branch.repository.get_graph()
109
 
    history = list(graph.iter_lefthand_ancestry(branch.last_revision(),
110
 
        [_mod_revision.NULL_REVISION]))
111
 
    for revision_id in reversed(history):
 
112
    for revision_id in branch.revision_history():
112
113
        this_inv = branch.repository.get_inventory(revision_id)
113
114
        if this_inv.has_id(file_id):
114
115
            this_ie = this_inv[file_id]
156
157
             end_revision=None,
157
158
             search=None,
158
159
             limit=None,
159
 
             show_diff=False,
160
 
             match=None):
 
160
             show_diff=False):
161
161
    """Write out human-readable log of commits to this branch.
162
162
 
163
163
    This function is being retained for backwards compatibility but
186
186
        if None or 0.
187
187
 
188
188
    :param show_diff: If True, output a diff after each revision.
189
 
 
190
 
    :param match: Dictionary of search lists to use when matching revision
191
 
      properties.
192
189
    """
193
190
    # Convert old-style parameters to new-style parameters
194
191
    if specific_fileid is not None:
218
215
    Logger(branch, rqst).show(lf)
219
216
 
220
217
 
221
 
# Note: This needs to be kept in sync with the defaults in
 
218
# Note: This needs to be kept this in sync with the defaults in
222
219
# make_log_request_dict() below
223
220
_DEFAULT_REQUEST_PARAMS = {
224
221
    'direction': 'reverse',
225
 
    'levels': None,
 
222
    'levels': 1,
226
223
    'generate_tags': True,
227
224
    'exclude_common_ancestry': False,
228
225
    '_match_using_deltas': True,
231
228
 
232
229
def make_log_request_dict(direction='reverse', specific_fileids=None,
233
230
                          start_revision=None, end_revision=None, limit=None,
234
 
                          message_search=None, levels=None, generate_tags=True,
 
231
                          message_search=None, levels=1, generate_tags=True,
235
232
                          delta_type=None,
236
233
                          diff_type=None, _match_using_deltas=True,
237
 
                          exclude_common_ancestry=False, match=None,
238
 
                          signature=False, omit_merges=False,
 
234
                          exclude_common_ancestry=False,
 
235
                          signature=False,
239
236
                          ):
240
237
    """Convenience function for making a logging request dictionary.
241
238
 
262
259
      matching commit messages
263
260
 
264
261
    :param levels: the number of levels of revisions to
265
 
      generate; 1 for just the mainline; 0 for all levels, or None for
266
 
      a sensible default.
 
262
      generate; 1 for just the mainline; 0 for all levels.
267
263
 
268
264
    :param generate_tags: If True, include tags for matched revisions.
269
265
`
286
282
      range operator or as a graph difference.
287
283
 
288
284
    :param signature: show digital signature information
289
 
 
290
 
    :param match: Dictionary of list of search strings to use when filtering
291
 
      revisions. Keys can be 'message', 'author', 'committer', 'bugs' or
292
 
      the empty string to match any of the preceding properties.
293
 
 
294
 
    :param omit_merges: If True, commits with more than one parent are
295
 
      omitted.
296
 
 
297
285
    """
298
 
    # Take care of old style message_search parameter
299
 
    if message_search:
300
 
        if match:
301
 
            if 'message' in match:
302
 
                match['message'].append(message_search)
303
 
            else:
304
 
                match['message'] = [message_search]
305
 
        else:
306
 
            match={ 'message': [message_search] }
307
286
    return {
308
287
        'direction': direction,
309
288
        'specific_fileids': specific_fileids,
310
289
        'start_revision': start_revision,
311
290
        'end_revision': end_revision,
312
291
        'limit': limit,
 
292
        'message_search': message_search,
313
293
        'levels': levels,
314
294
        'generate_tags': generate_tags,
315
295
        'delta_type': delta_type,
316
296
        'diff_type': diff_type,
317
297
        'exclude_common_ancestry': exclude_common_ancestry,
318
298
        'signature': signature,
319
 
        'match': match,
320
 
        'omit_merges': omit_merges,
321
299
        # Add 'private' attributes for features that may be deprecated
322
300
        '_match_using_deltas': _match_using_deltas,
323
301
    }
333
311
 
334
312
def format_signature_validity(rev_id, repo):
335
313
    """get the signature validity
336
 
 
 
314
    
337
315
    :param rev_id: revision id to validate
338
316
    :param repo: repository of revision
339
317
    :return: human readable string to print to log
341
319
    from bzrlib import gpg
342
320
 
343
321
    gpg_strategy = gpg.GPGStrategy(None)
344
 
    result = repo.verify_revision_signature(rev_id, gpg_strategy)
 
322
    result = repo.verify_revision(rev_id, gpg_strategy)
345
323
    if result[0] == gpg.SIGNATURE_VALID:
346
324
        return "valid signature from {0}".format(result[1])
347
325
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
402
380
        # Tweak the LogRequest based on what the LogFormatter can handle.
403
381
        # (There's no point generating stuff if the formatter can't display it.)
404
382
        rqst = self.rqst
405
 
        if rqst['levels'] is None or lf.get_levels() > rqst['levels']:
406
 
            # user didn't specify levels, use whatever the LF can handle:
407
 
            rqst['levels'] = lf.get_levels()
408
 
 
 
383
        rqst['levels'] = lf.get_levels()
409
384
        if not getattr(lf, 'supports_tags', False):
410
385
            rqst['generate_tags'] = False
411
386
        if not getattr(lf, 'supports_delta', False):
423
398
 
424
399
    def _generator_factory(self, branch, rqst):
425
400
        """Make the LogGenerator object to use.
426
 
 
 
401
        
427
402
        Subclasses may wish to override this.
428
403
        """
429
404
        return _DefaultLogGenerator(branch, rqst)
454
429
        limit = rqst.get('limit')
455
430
        diff_type = rqst.get('diff_type')
456
431
        show_signature = rqst.get('signature')
457
 
        omit_merges = rqst.get('omit_merges')
458
432
        log_count = 0
459
433
        revision_iterator = self._create_log_revision_iterator()
460
434
        for revs in revision_iterator:
462
436
                # 0 levels means show everything; merge_depth counts from 0
463
437
                if levels != 0 and merge_depth >= levels:
464
438
                    continue
465
 
                if omit_merges and len(rev.parent_ids) > 1:
466
 
                    continue
467
439
                if diff_type is None:
468
440
                    diff = None
469
441
                else:
535
507
 
536
508
        # Apply the other filters
537
509
        return make_log_rev_iterator(self.branch, view_revisions,
538
 
            rqst.get('delta_type'), rqst.get('match'),
 
510
            rqst.get('delta_type'), rqst.get('message_search'),
539
511
            file_ids=rqst.get('specific_fileids'),
540
512
            direction=rqst.get('direction'))
541
513
 
554
526
            rqst.get('specific_fileids')[0], view_revisions,
555
527
            include_merges=rqst.get('levels') != 1)
556
528
        return make_log_rev_iterator(self.branch, view_revisions,
557
 
            rqst.get('delta_type'), rqst.get('match'))
 
529
            rqst.get('delta_type'), rqst.get('message_search'))
558
530
 
559
531
 
560
532
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
568
540
             a list of the same tuples.
569
541
    """
570
542
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
571
 
        raise errors.BzrCommandError(gettext(
572
 
            '--exclude-common-ancestry requires two different revisions'))
 
543
        raise errors.BzrCommandError(
 
544
            '--exclude-common-ancestry requires two different revisions')
573
545
    if direction not in ('reverse', 'forward'):
574
 
        raise ValueError(gettext('invalid direction %r') % direction)
 
546
        raise ValueError('invalid direction %r' % direction)
575
547
    br_revno, br_rev_id = branch.last_revision_info()
576
548
    if br_revno == 0:
577
549
        return []
580
552
        and (not generate_merge_revisions
581
553
             or not _has_merges(branch, end_rev_id))):
582
554
        # If a single revision is requested, check we can handle it
583
 
        return  _generate_one_revision(branch, end_rev_id, br_rev_id,
584
 
                                       br_revno)
585
 
    if not generate_merge_revisions:
586
 
        try:
587
 
            # If we only want to see linear revisions, we can iterate ...
588
 
            iter_revs = _linear_view_revisions(
589
 
                branch, start_rev_id, end_rev_id,
590
 
                exclude_common_ancestry=exclude_common_ancestry)
591
 
            # If a start limit was given and it's not obviously an
592
 
            # ancestor of the end limit, check it before outputting anything
593
 
            if (direction == 'forward'
594
 
                or (start_rev_id and not _is_obvious_ancestor(
595
 
                        branch, start_rev_id, end_rev_id))):
596
 
                    iter_revs = list(iter_revs)
597
 
            if direction == 'forward':
598
 
                iter_revs = reversed(iter_revs)
599
 
            return iter_revs
600
 
        except _StartNotLinearAncestor:
601
 
            # Switch to the slower implementation that may be able to find a
602
 
            # non-obvious ancestor out of the left-hand history.
603
 
            pass
604
 
    iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
605
 
                                        direction, delayed_graph_generation,
606
 
                                        exclude_common_ancestry)
607
 
    if direction == 'forward':
608
 
        iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
 
555
        iter_revs = _generate_one_revision(branch, end_rev_id, br_rev_id,
 
556
                                           br_revno)
 
557
    elif not generate_merge_revisions:
 
558
        # If we only want to see linear revisions, we can iterate ...
 
559
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
 
560
                                             direction, exclude_common_ancestry)
 
561
        if direction == 'forward':
 
562
            iter_revs = reversed(iter_revs)
 
563
    else:
 
564
        iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
 
565
                                            direction, delayed_graph_generation,
 
566
                                            exclude_common_ancestry)
 
567
        if direction == 'forward':
 
568
            iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
609
569
    return iter_revs
610
570
 
611
571
 
618
578
        return [(rev_id, revno_str, 0)]
619
579
 
620
580
 
 
581
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
 
582
                             exclude_common_ancestry=False):
 
583
    result = _linear_view_revisions(
 
584
        branch, start_rev_id, end_rev_id,
 
585
        exclude_common_ancestry=exclude_common_ancestry)
 
586
    # If a start limit was given and it's not obviously an
 
587
    # ancestor of the end limit, check it before outputting anything
 
588
    if direction == 'forward' or (start_rev_id
 
589
        and not _is_obvious_ancestor(branch, start_rev_id, end_rev_id)):
 
590
        try:
 
591
            result = list(result)
 
592
        except _StartNotLinearAncestor:
 
593
            raise errors.BzrCommandError('Start revision not found in'
 
594
                ' left-hand history of end revision.')
 
595
    return result
 
596
 
 
597
 
621
598
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
622
599
                            delayed_graph_generation,
623
600
                            exclude_common_ancestry=False):
659
636
        except _StartNotLinearAncestor:
660
637
            # A merge was never detected so the lower revision limit can't
661
638
            # be nested down somewhere
662
 
            raise errors.BzrCommandError(gettext('Start revision not found in'
663
 
                ' history of end revision.'))
 
639
            raise errors.BzrCommandError('Start revision not found in'
 
640
                ' history of end revision.')
664
641
 
665
642
    # We exit the loop above because we encounter a revision with merges, from
666
643
    # this revision, we need to switch to _graph_view_revisions.
806
783
            yield rev_id, '.'.join(map(str, revno)), merge_depth
807
784
 
808
785
 
 
786
@deprecated_function(deprecated_in((2, 2, 0)))
 
787
def calculate_view_revisions(branch, start_revision, end_revision, direction,
 
788
        specific_fileid, generate_merge_revisions):
 
789
    """Calculate the revisions to view.
 
790
 
 
791
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
 
792
             a list of the same tuples.
 
793
    """
 
794
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
 
795
        end_revision)
 
796
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
 
797
        direction, generate_merge_revisions or specific_fileid))
 
798
    if specific_fileid:
 
799
        view_revisions = _filter_revisions_touching_file_id(branch,
 
800
            specific_fileid, view_revisions,
 
801
            include_merges=generate_merge_revisions)
 
802
    return _rebase_merge_depth(view_revisions)
 
803
 
 
804
 
809
805
def _rebase_merge_depth(view_revisions):
810
806
    """Adjust depths upwards so the top level is 0."""
811
807
    # If either the first or last revision have a merge_depth of 0, we're done
855
851
    return log_rev_iterator
856
852
 
857
853
 
858
 
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
 
854
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
859
855
    """Create a filtered iterator of log_rev_iterator matching on a regex.
860
856
 
861
857
    :param branch: The branch being logged.
862
858
    :param generate_delta: Whether to generate a delta for each revision.
863
 
    :param match: A dictionary with properties as keys and lists of strings
864
 
        as values. To match, a revision may match any of the supplied strings
865
 
        within a single property but must match at least one string for each
866
 
        property.
 
859
    :param search: A user text search string.
867
860
    :param log_rev_iterator: An input iterator containing all revisions that
868
861
        could be displayed, in lists.
869
862
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
870
863
        delta).
871
864
    """
872
 
    if match is None:
 
865
    if search is None:
873
866
        return log_rev_iterator
874
 
    searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
875
 
                for (k,v) in match.iteritems()]
876
 
    return _filter_re(searchRE, log_rev_iterator)
877
 
 
878
 
 
879
 
def _filter_re(searchRE, log_rev_iterator):
 
867
    searchRE = lazy_regex.lazy_compile(search, re.IGNORECASE)
 
868
    return _filter_message_re(searchRE, log_rev_iterator)
 
869
 
 
870
 
 
871
def _filter_message_re(searchRE, log_rev_iterator):
880
872
    for revs in log_rev_iterator:
881
 
        new_revs = [rev for rev in revs if _match_filter(searchRE, rev[1])]
882
 
        if new_revs:
883
 
            yield new_revs
884
 
 
885
 
def _match_filter(searchRE, rev):
886
 
    strings = {
887
 
               'message': (rev.message,),
888
 
               'committer': (rev.committer,),
889
 
               'author': (rev.get_apparent_authors()),
890
 
               'bugs': list(rev.iter_bugs())
891
 
               }
892
 
    strings[''] = [item for inner_list in strings.itervalues()
893
 
                   for item in inner_list]
894
 
    for (k,v) in searchRE:
895
 
        if k in strings and not _match_any_filter(strings[k], v):
896
 
            return False
897
 
    return True
898
 
 
899
 
def _match_any_filter(strings, res):
900
 
    return any([filter(None, map(re.search, strings)) for re in res])
 
873
        new_revs = []
 
874
        for (rev_id, revno, merge_depth), rev, delta in revs:
 
875
            if searchRE.search(rev.message):
 
876
                new_revs.append(((rev_id, revno, merge_depth), rev, delta))
 
877
        yield new_revs
 
878
 
901
879
 
902
880
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
903
881
    fileids=None, direction='reverse'):
976
954
 
977
955
def _update_fileids(delta, fileids, stop_on):
978
956
    """Update the set of file-ids to search based on file lifecycle events.
979
 
 
 
957
    
980
958
    :param fileids: a set of fileids to update
981
959
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
982
960
      fileids set once their add or remove entry is detected respectively
1023
1001
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
1024
1002
        delta).
1025
1003
    """
 
1004
    repository = branch.repository
1026
1005
    num = 9
1027
1006
    for batch in log_rev_iterator:
1028
1007
        batch = iter(batch)
1077
1056
    if branch_revno != 0:
1078
1057
        if (start_rev_id == _mod_revision.NULL_REVISION
1079
1058
            or end_rev_id == _mod_revision.NULL_REVISION):
1080
 
            raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
 
1059
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
1081
1060
        if start_revno > end_revno:
1082
 
            raise errors.BzrCommandError(gettext("Start revision must be "
1083
 
                                         "older than the end revision."))
 
1061
            raise errors.BzrCommandError("Start revision must be older than "
 
1062
                                         "the end revision.")
1084
1063
    return (start_rev_id, end_rev_id)
1085
1064
 
1086
1065
 
1135
1114
 
1136
1115
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1137
1116
        or (end_rev_id == _mod_revision.NULL_REVISION)):
1138
 
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
 
1117
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
1139
1118
    if start_revno > end_revno:
1140
 
        raise errors.BzrCommandError(gettext("Start revision must be older "
1141
 
                                     "than the end revision."))
 
1119
        raise errors.BzrCommandError("Start revision must be older than "
 
1120
                                     "the end revision.")
1142
1121
 
1143
1122
    if end_revno < start_revno:
1144
1123
        return None, None, None, None
1167
1146
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1168
1147
 
1169
1148
 
 
1149
@deprecated_function(deprecated_in((2, 2, 0)))
 
1150
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
 
1151
    """Filter view_revisions based on revision ranges.
 
1152
 
 
1153
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
 
1154
            tuples to be filtered.
 
1155
 
 
1156
    :param start_rev_id: If not NONE specifies the first revision to be logged.
 
1157
            If NONE then all revisions up to the end_rev_id are logged.
 
1158
 
 
1159
    :param end_rev_id: If not NONE specifies the last revision to be logged.
 
1160
            If NONE then all revisions up to the end of the log are logged.
 
1161
 
 
1162
    :return: The filtered view_revisions.
 
1163
    """
 
1164
    if start_rev_id or end_rev_id:
 
1165
        revision_ids = [r for r, n, d in view_revisions]
 
1166
        if start_rev_id:
 
1167
            start_index = revision_ids.index(start_rev_id)
 
1168
        else:
 
1169
            start_index = 0
 
1170
        if start_rev_id == end_rev_id:
 
1171
            end_index = start_index
 
1172
        else:
 
1173
            if end_rev_id:
 
1174
                end_index = revision_ids.index(end_rev_id)
 
1175
            else:
 
1176
                end_index = len(view_revisions) - 1
 
1177
        # To include the revisions merged into the last revision,
 
1178
        # extend end_rev_id down to, but not including, the next rev
 
1179
        # with the same or lesser merge_depth
 
1180
        end_merge_depth = view_revisions[end_index][2]
 
1181
        try:
 
1182
            for index in xrange(end_index+1, len(view_revisions)+1):
 
1183
                if view_revisions[index][2] <= end_merge_depth:
 
1184
                    end_index = index - 1
 
1185
                    break
 
1186
        except IndexError:
 
1187
            # if the search falls off the end then log to the end as well
 
1188
            end_index = len(view_revisions) - 1
 
1189
        view_revisions = view_revisions[start_index:end_index+1]
 
1190
    return view_revisions
 
1191
 
 
1192
 
1170
1193
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1171
1194
    include_merges=True):
1172
1195
    r"""Return the list of revision ids which touch a given file id.
1251
1274
    return result
1252
1275
 
1253
1276
 
 
1277
@deprecated_function(deprecated_in((2, 2, 0)))
 
1278
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
 
1279
                       include_merges=True):
 
1280
    """Produce an iterator of revisions to show
 
1281
    :return: an iterator of (revision_id, revno, merge_depth)
 
1282
    (if there is no revno for a revision, None is supplied)
 
1283
    """
 
1284
    if not include_merges:
 
1285
        revision_ids = mainline_revs[1:]
 
1286
        if direction == 'reverse':
 
1287
            revision_ids.reverse()
 
1288
        for revision_id in revision_ids:
 
1289
            yield revision_id, str(rev_nos[revision_id]), 0
 
1290
        return
 
1291
    graph = branch.repository.get_graph()
 
1292
    # This asks for all mainline revisions, which means we only have to spider
 
1293
    # sideways, rather than depth history. That said, its still size-of-history
 
1294
    # and should be addressed.
 
1295
    # mainline_revisions always includes an extra revision at the beginning, so
 
1296
    # don't request it.
 
1297
    parent_map = dict(((key, value) for key, value in
 
1298
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
 
1299
    # filter out ghosts; merge_sort errors on ghosts.
 
1300
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
 
1301
    merge_sorted_revisions = tsort.merge_sort(
 
1302
        rev_graph,
 
1303
        mainline_revs[-1],
 
1304
        mainline_revs,
 
1305
        generate_revno=True)
 
1306
 
 
1307
    if direction == 'forward':
 
1308
        # forward means oldest first.
 
1309
        merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
 
1310
    elif direction != 'reverse':
 
1311
        raise ValueError('invalid direction %r' % direction)
 
1312
 
 
1313
    for (sequence, rev_id, merge_depth, revno, end_of_merge
 
1314
         ) in merge_sorted_revisions:
 
1315
        yield rev_id, '.'.join(map(str, revno)), merge_depth
 
1316
 
 
1317
 
1254
1318
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1255
1319
    """Reverse revisions by depth.
1256
1320
 
1353
1417
        """Create a LogFormatter.
1354
1418
 
1355
1419
        :param to_file: the file to output to
1356
 
        :param to_exact_file: if set, gives an output stream to which
 
1420
        :param to_exact_file: if set, gives an output stream to which 
1357
1421
             non-Unicode diffs are written.
1358
1422
        :param show_ids: if True, revision-ids are to be displayed
1359
1423
        :param show_timezone: the timezone to use
1410
1474
            if advice_sep:
1411
1475
                self.to_file.write(advice_sep)
1412
1476
            self.to_file.write(
1413
 
                "Use --include-merged or -n0 to see merged revisions.\n")
 
1477
                "Use --include-merges or -n0 to see merged revisions.\n")
1414
1478
 
1415
1479
    def get_advice_separator(self):
1416
1480
        """Get the text separating the log from the closing advice."""
1595
1659
        if revision.delta is not None:
1596
1660
            # Use the standard status output to display changes
1597
1661
            from bzrlib.delta import report_delta
1598
 
            report_delta(to_file, revision.delta, short_status=False,
 
1662
            report_delta(to_file, revision.delta, short_status=False, 
1599
1663
                         show_ids=self.show_ids, indent=indent)
1600
1664
        if revision.diff is not None:
1601
1665
            to_file.write(indent + 'diff:\n')
1667
1731
        if revision.delta is not None:
1668
1732
            # Use the standard status output to display changes
1669
1733
            from bzrlib.delta import report_delta
1670
 
            report_delta(to_file, revision.delta,
1671
 
                         short_status=self.delta_format==1,
 
1734
            report_delta(to_file, revision.delta, 
 
1735
                         short_status=self.delta_format==1, 
1672
1736
                         show_ids=self.show_ids, indent=indent + offset)
1673
1737
        if revision.diff is not None:
1674
1738
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1794
1858
        return self.get(name)(*args, **kwargs)
1795
1859
 
1796
1860
    def get_default(self, branch):
1797
 
        c = branch.get_config_stack()
1798
 
        return self.get(c.get('log_format'))
 
1861
        return self.get(branch.get_config().log_format())
1799
1862
 
1800
1863
 
1801
1864
log_formatter_registry = LogFormatterRegistry()
1802
1865
 
1803
1866
 
1804
1867
log_formatter_registry.register('short', ShortLogFormatter,
1805
 
                                'Moderately short log format.')
 
1868
                                'Moderately short log format')
1806
1869
log_formatter_registry.register('long', LongLogFormatter,
1807
 
                                'Detailed log format.')
 
1870
                                'Detailed log format')
1808
1871
log_formatter_registry.register('line', LineLogFormatter,
1809
 
                                'Log format with one line per revision.')
 
1872
                                'Log format with one line per revision')
1810
1873
log_formatter_registry.register('gnu-changelog', GnuChangelogLogFormatter,
1811
 
                                'Format used by GNU ChangeLog files.')
 
1874
                                'Format used by GNU ChangeLog files')
1812
1875
 
1813
1876
 
1814
1877
def register_formatter(name, formatter):
1824
1887
    try:
1825
1888
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1826
1889
    except KeyError:
1827
 
        raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
 
1890
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1828
1891
 
1829
1892
 
1830
1893
def author_list_all(rev):
1855
1918
                              'The committer')
1856
1919
 
1857
1920
 
 
1921
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
 
1922
    # deprecated; for compatibility
 
1923
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
 
1924
    lf.show(revno, rev, delta)
 
1925
 
 
1926
 
1858
1927
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1859
1928
                           log_format='long'):
1860
1929
    """Show the change in revision history comparing the old revision history to the new one.
2023
2092
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2024
2093
      branch will be read-locked.
2025
2094
    """
2026
 
    from bzrlib.builtins import _get_revision_range
2027
 
    tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2028
 
        file_list[0])
 
2095
    from builtins import _get_revision_range
 
2096
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2029
2097
    add_cleanup(b.lock_read().unlock)
2030
2098
    # XXX: It's damn messy converting a list of paths to relative paths when
2031
2099
    # those paths might be deleted ones, they might be on a case-insensitive
2120
2188
                          len(row) > 1 and row[1] == 'fixed']
2121
2189
 
2122
2190
        if fixed_bug_urls:
2123
 
            return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
2124
 
                    ' '.join(fixed_bug_urls)}
 
2191
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
2125
2192
    return {}
2126
2193
 
2127
2194
properties_handler_registry.register('bugs_properties_handler',