~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Vincent Ladeuil
  • Date: 2012-07-31 09:17:34 UTC
  • mto: This revision was merged to the branch mainline in revision 6554.
  • Revision ID: v.ladeuil+lp@free.fr-20120731091734-700oburs0806cont
SplitĀ eagerĀ test.

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
 
 
19
17
"""Code to show logs of changes.
20
18
 
21
19
Various flavors of log can be produced:
49
47
all the changes since the previous revision that touched hello.c.
50
48
"""
51
49
 
 
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,
69
68
    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,
78
77
    )
 
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
 
    )
96
92
 
97
93
 
98
94
def find_touching_revisions(branch, file_id):
109
105
    last_ie = None
110
106
    last_path = None
111
107
    revno = 1
112
 
    for revision_id in branch.revision_history():
 
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):
113
112
        this_inv = branch.repository.get_inventory(revision_id)
114
113
        if this_inv.has_id(file_id):
115
114
            this_ie = this_inv[file_id]
157
156
             end_revision=None,
158
157
             search=None,
159
158
             limit=None,
160
 
             show_diff=False):
 
159
             show_diff=False,
 
160
             match=None):
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.
189
192
    """
190
193
    # Convert old-style parameters to new-style parameters
191
194
    if specific_fileid is not None:
215
218
    Logger(branch, rqst).show(lf)
216
219
 
217
220
 
218
 
# Note: This needs to be kept this in sync with the defaults in
 
221
# Note: This needs to be kept in sync with the defaults in
219
222
# make_log_request_dict() below
220
223
_DEFAULT_REQUEST_PARAMS = {
221
224
    'direction': 'reverse',
222
 
    'levels': 1,
 
225
    'levels': None,
223
226
    'generate_tags': True,
224
227
    'exclude_common_ancestry': False,
225
228
    '_match_using_deltas': True,
228
231
 
229
232
def make_log_request_dict(direction='reverse', specific_fileids=None,
230
233
                          start_revision=None, end_revision=None, limit=None,
231
 
                          message_search=None, levels=1, generate_tags=True,
 
234
                          message_search=None, levels=None, generate_tags=True,
232
235
                          delta_type=None,
233
236
                          diff_type=None, _match_using_deltas=True,
234
 
                          exclude_common_ancestry=False,
235
 
                          signature=False,
 
237
                          exclude_common_ancestry=False, match=None,
 
238
                          signature=False, omit_merges=False,
236
239
                          ):
237
240
    """Convenience function for making a logging request dictionary.
238
241
 
259
262
      matching commit messages
260
263
 
261
264
    :param levels: the number of levels of revisions to
262
 
      generate; 1 for just the mainline; 0 for all levels.
 
265
      generate; 1 for just the mainline; 0 for all levels, or None for
 
266
      a sensible default.
263
267
 
264
268
    :param generate_tags: If True, include tags for matched revisions.
265
269
`
282
286
      range operator or as a graph difference.
283
287
 
284
288
    :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
 
285
297
    """
 
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] }
286
307
    return {
287
308
        'direction': direction,
288
309
        'specific_fileids': specific_fileids,
289
310
        'start_revision': start_revision,
290
311
        'end_revision': end_revision,
291
312
        'limit': limit,
292
 
        'message_search': message_search,
293
313
        'levels': levels,
294
314
        'generate_tags': generate_tags,
295
315
        'delta_type': delta_type,
296
316
        'diff_type': diff_type,
297
317
        'exclude_common_ancestry': exclude_common_ancestry,
298
318
        'signature': signature,
 
319
        'match': match,
 
320
        'omit_merges': omit_merges,
299
321
        # Add 'private' attributes for features that may be deprecated
300
322
        '_match_using_deltas': _match_using_deltas,
301
323
    }
311
333
 
312
334
def format_signature_validity(rev_id, repo):
313
335
    """get the signature validity
314
 
    
 
336
 
315
337
    :param rev_id: revision id to validate
316
338
    :param repo: repository of revision
317
339
    :return: human readable string to print to log
319
341
    from bzrlib import gpg
320
342
 
321
343
    gpg_strategy = gpg.GPGStrategy(None)
322
 
    result = repo.verify_revision(rev_id, gpg_strategy)
 
344
    result = repo.verify_revision_signature(rev_id, gpg_strategy)
323
345
    if result[0] == gpg.SIGNATURE_VALID:
324
346
        return "valid signature from {0}".format(result[1])
325
347
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
380
402
        # Tweak the LogRequest based on what the LogFormatter can handle.
381
403
        # (There's no point generating stuff if the formatter can't display it.)
382
404
        rqst = self.rqst
383
 
        rqst['levels'] = lf.get_levels()
 
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
 
384
409
        if not getattr(lf, 'supports_tags', False):
385
410
            rqst['generate_tags'] = False
386
411
        if not getattr(lf, 'supports_delta', False):
398
423
 
399
424
    def _generator_factory(self, branch, rqst):
400
425
        """Make the LogGenerator object to use.
401
 
        
 
426
 
402
427
        Subclasses may wish to override this.
403
428
        """
404
429
        return _DefaultLogGenerator(branch, rqst)
429
454
        limit = rqst.get('limit')
430
455
        diff_type = rqst.get('diff_type')
431
456
        show_signature = rqst.get('signature')
 
457
        omit_merges = rqst.get('omit_merges')
432
458
        log_count = 0
433
459
        revision_iterator = self._create_log_revision_iterator()
434
460
        for revs in revision_iterator:
436
462
                # 0 levels means show everything; merge_depth counts from 0
437
463
                if levels != 0 and merge_depth >= levels:
438
464
                    continue
 
465
                if omit_merges and len(rev.parent_ids) > 1:
 
466
                    continue
439
467
                if diff_type is None:
440
468
                    diff = None
441
469
                else:
507
535
 
508
536
        # Apply the other filters
509
537
        return make_log_rev_iterator(self.branch, view_revisions,
510
 
            rqst.get('delta_type'), rqst.get('message_search'),
 
538
            rqst.get('delta_type'), rqst.get('match'),
511
539
            file_ids=rqst.get('specific_fileids'),
512
540
            direction=rqst.get('direction'))
513
541
 
526
554
            rqst.get('specific_fileids')[0], view_revisions,
527
555
            include_merges=rqst.get('levels') != 1)
528
556
        return make_log_rev_iterator(self.branch, view_revisions,
529
 
            rqst.get('delta_type'), rqst.get('message_search'))
 
557
            rqst.get('delta_type'), rqst.get('match'))
530
558
 
531
559
 
532
560
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
540
568
             a list of the same tuples.
541
569
    """
542
570
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
543
 
        raise errors.BzrCommandError(
544
 
            '--exclude-common-ancestry requires two different revisions')
 
571
        raise errors.BzrCommandError(gettext(
 
572
            '--exclude-common-ancestry requires two different revisions'))
545
573
    if direction not in ('reverse', 'forward'):
546
 
        raise ValueError('invalid direction %r' % direction)
 
574
        raise ValueError(gettext('invalid direction %r') % direction)
547
575
    br_revno, br_rev_id = branch.last_revision_info()
548
576
    if br_revno == 0:
549
577
        return []
552
580
        and (not generate_merge_revisions
553
581
             or not _has_merges(branch, end_rev_id))):
554
582
        # If a single revision is requested, check we can handle it
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)))
 
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)))
569
609
    return iter_revs
570
610
 
571
611
 
578
618
        return [(rev_id, revno_str, 0)]
579
619
 
580
620
 
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
 
 
598
621
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
599
622
                            delayed_graph_generation,
600
623
                            exclude_common_ancestry=False):
636
659
        except _StartNotLinearAncestor:
637
660
            # A merge was never detected so the lower revision limit can't
638
661
            # be nested down somewhere
639
 
            raise errors.BzrCommandError('Start revision not found in'
640
 
                ' history of end revision.')
 
662
            raise errors.BzrCommandError(gettext('Start revision not found in'
 
663
                ' history of end revision.'))
641
664
 
642
665
    # We exit the loop above because we encounter a revision with merges, from
643
666
    # this revision, we need to switch to _graph_view_revisions.
783
806
            yield rev_id, '.'.join(map(str, revno)), merge_depth
784
807
 
785
808
 
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
 
 
805
809
def _rebase_merge_depth(view_revisions):
806
810
    """Adjust depths upwards so the top level is 0."""
807
811
    # If either the first or last revision have a merge_depth of 0, we're done
851
855
    return log_rev_iterator
852
856
 
853
857
 
854
 
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
 
858
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
855
859
    """Create a filtered iterator of log_rev_iterator matching on a regex.
856
860
 
857
861
    :param branch: The branch being logged.
858
862
    :param generate_delta: Whether to generate a delta for each revision.
859
 
    :param search: A user text search string.
 
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.
860
867
    :param log_rev_iterator: An input iterator containing all revisions that
861
868
        could be displayed, in lists.
862
869
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
863
870
        delta).
864
871
    """
865
 
    if search is None:
 
872
    if match is None:
866
873
        return 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):
 
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):
872
880
    for revs in log_rev_iterator:
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
 
 
 
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])
879
901
 
880
902
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
881
903
    fileids=None, direction='reverse'):
954
976
 
955
977
def _update_fileids(delta, fileids, stop_on):
956
978
    """Update the set of file-ids to search based on file lifecycle events.
957
 
    
 
979
 
958
980
    :param fileids: a set of fileids to update
959
981
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
960
982
      fileids set once their add or remove entry is detected respectively
1001
1023
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
1002
1024
        delta).
1003
1025
    """
1004
 
    repository = branch.repository
1005
1026
    num = 9
1006
1027
    for batch in log_rev_iterator:
1007
1028
        batch = iter(batch)
1056
1077
    if branch_revno != 0:
1057
1078
        if (start_rev_id == _mod_revision.NULL_REVISION
1058
1079
            or end_rev_id == _mod_revision.NULL_REVISION):
1059
 
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
1080
            raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1060
1081
        if start_revno > end_revno:
1061
 
            raise errors.BzrCommandError("Start revision must be older than "
1062
 
                                         "the end revision.")
 
1082
            raise errors.BzrCommandError(gettext("Start revision must be "
 
1083
                                         "older than the end revision."))
1063
1084
    return (start_rev_id, end_rev_id)
1064
1085
 
1065
1086
 
1114
1135
 
1115
1136
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1116
1137
        or (end_rev_id == _mod_revision.NULL_REVISION)):
1117
 
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
1138
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1118
1139
    if start_revno > end_revno:
1119
 
        raise errors.BzrCommandError("Start revision must be older than "
1120
 
                                     "the end revision.")
 
1140
        raise errors.BzrCommandError(gettext("Start revision must be older "
 
1141
                                     "than the end revision."))
1121
1142
 
1122
1143
    if end_revno < start_revno:
1123
1144
        return None, None, None, None
1146
1167
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1147
1168
 
1148
1169
 
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
 
 
1193
1170
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1194
1171
    include_merges=True):
1195
1172
    r"""Return the list of revision ids which touch a given file id.
1274
1251
    return result
1275
1252
 
1276
1253
 
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
 
 
1318
1254
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1319
1255
    """Reverse revisions by depth.
1320
1256
 
1417
1353
        """Create a LogFormatter.
1418
1354
 
1419
1355
        :param to_file: the file to output to
1420
 
        :param to_exact_file: if set, gives an output stream to which 
 
1356
        :param to_exact_file: if set, gives an output stream to which
1421
1357
             non-Unicode diffs are written.
1422
1358
        :param show_ids: if True, revision-ids are to be displayed
1423
1359
        :param show_timezone: the timezone to use
1474
1410
            if advice_sep:
1475
1411
                self.to_file.write(advice_sep)
1476
1412
            self.to_file.write(
1477
 
                "Use --include-merges or -n0 to see merged revisions.\n")
 
1413
                "Use --include-merged or -n0 to see merged revisions.\n")
1478
1414
 
1479
1415
    def get_advice_separator(self):
1480
1416
        """Get the text separating the log from the closing advice."""
1659
1595
        if revision.delta is not None:
1660
1596
            # Use the standard status output to display changes
1661
1597
            from bzrlib.delta import report_delta
1662
 
            report_delta(to_file, revision.delta, short_status=False, 
 
1598
            report_delta(to_file, revision.delta, short_status=False,
1663
1599
                         show_ids=self.show_ids, indent=indent)
1664
1600
        if revision.diff is not None:
1665
1601
            to_file.write(indent + 'diff:\n')
1731
1667
        if revision.delta is not None:
1732
1668
            # Use the standard status output to display changes
1733
1669
            from bzrlib.delta import report_delta
1734
 
            report_delta(to_file, revision.delta, 
1735
 
                         short_status=self.delta_format==1, 
 
1670
            report_delta(to_file, revision.delta,
 
1671
                         short_status=self.delta_format==1,
1736
1672
                         show_ids=self.show_ids, indent=indent + offset)
1737
1673
        if revision.diff is not None:
1738
1674
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1858
1794
        return self.get(name)(*args, **kwargs)
1859
1795
 
1860
1796
    def get_default(self, branch):
1861
 
        return self.get(branch.get_config().log_format())
 
1797
        c = branch.get_config_stack()
 
1798
        return self.get(c.get('log_format'))
1862
1799
 
1863
1800
 
1864
1801
log_formatter_registry = LogFormatterRegistry()
1865
1802
 
1866
1803
 
1867
1804
log_formatter_registry.register('short', ShortLogFormatter,
1868
 
                                'Moderately short log format')
 
1805
                                'Moderately short log format.')
1869
1806
log_formatter_registry.register('long', LongLogFormatter,
1870
 
                                'Detailed log format')
 
1807
                                'Detailed log format.')
1871
1808
log_formatter_registry.register('line', LineLogFormatter,
1872
 
                                'Log format with one line per revision')
 
1809
                                'Log format with one line per revision.')
1873
1810
log_formatter_registry.register('gnu-changelog', GnuChangelogLogFormatter,
1874
 
                                'Format used by GNU ChangeLog files')
 
1811
                                'Format used by GNU ChangeLog files.')
1875
1812
 
1876
1813
 
1877
1814
def register_formatter(name, formatter):
1887
1824
    try:
1888
1825
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1889
1826
    except KeyError:
1890
 
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
 
1827
        raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
1891
1828
 
1892
1829
 
1893
1830
def author_list_all(rev):
1918
1855
                              'The committer')
1919
1856
 
1920
1857
 
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
 
 
1927
1858
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1928
1859
                           log_format='long'):
1929
1860
    """Show the change in revision history comparing the old revision history to the new one.
2092
2023
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2093
2024
      branch will be read-locked.
2094
2025
    """
2095
 
    from builtins import _get_revision_range
2096
 
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
 
2026
    from bzrlib.builtins import _get_revision_range
 
2027
    tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
 
2028
        file_list[0])
2097
2029
    add_cleanup(b.lock_read().unlock)
2098
2030
    # XXX: It's damn messy converting a list of paths to relative paths when
2099
2031
    # those paths might be deleted ones, they might be on a case-insensitive
2188
2120
                          len(row) > 1 and row[1] == 'fixed']
2189
2121
 
2190
2122
        if fixed_bug_urls:
2191
 
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
 
2123
            return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
 
2124
                    ' '.join(fixed_bug_urls)}
2192
2125
    return {}
2193
2126
 
2194
2127
properties_handler_registry.register('bugs_properties_handler',