~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

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]
139
138
        revno += 1
140
139
 
141
140
 
142
 
def _enumerate_history(branch):
143
 
    rh = []
144
 
    revno = 1
145
 
    for rev_id in branch.revision_history():
146
 
        rh.append((revno, rev_id))
147
 
        revno += 1
148
 
    return rh
149
 
 
150
 
 
151
141
def show_log(branch,
152
142
             lf,
153
143
             specific_fileid=None,
157
147
             end_revision=None,
158
148
             search=None,
159
149
             limit=None,
160
 
             show_diff=False):
 
150
             show_diff=False,
 
151
             match=None):
161
152
    """Write out human-readable log of commits to this branch.
162
153
 
163
154
    This function is being retained for backwards compatibility but
186
177
        if None or 0.
187
178
 
188
179
    :param show_diff: If True, output a diff after each revision.
 
180
 
 
181
    :param match: Dictionary of search lists to use when matching revision
 
182
      properties.
189
183
    """
190
184
    # Convert old-style parameters to new-style parameters
191
185
    if specific_fileid is not None:
215
209
    Logger(branch, rqst).show(lf)
216
210
 
217
211
 
218
 
# Note: This needs to be kept this in sync with the defaults in
 
212
# Note: This needs to be kept in sync with the defaults in
219
213
# make_log_request_dict() below
220
214
_DEFAULT_REQUEST_PARAMS = {
221
215
    'direction': 'reverse',
222
 
    'levels': 1,
 
216
    'levels': None,
223
217
    'generate_tags': True,
224
218
    'exclude_common_ancestry': False,
225
219
    '_match_using_deltas': True,
228
222
 
229
223
def make_log_request_dict(direction='reverse', specific_fileids=None,
230
224
                          start_revision=None, end_revision=None, limit=None,
231
 
                          message_search=None, levels=1, generate_tags=True,
 
225
                          message_search=None, levels=None, generate_tags=True,
232
226
                          delta_type=None,
233
227
                          diff_type=None, _match_using_deltas=True,
234
 
                          exclude_common_ancestry=False,
235
 
                          signature=False,
 
228
                          exclude_common_ancestry=False, match=None,
 
229
                          signature=False, omit_merges=False,
236
230
                          ):
237
231
    """Convenience function for making a logging request dictionary.
238
232
 
259
253
      matching commit messages
260
254
 
261
255
    :param levels: the number of levels of revisions to
262
 
      generate; 1 for just the mainline; 0 for all levels.
 
256
      generate; 1 for just the mainline; 0 for all levels, or None for
 
257
      a sensible default.
263
258
 
264
259
    :param generate_tags: If True, include tags for matched revisions.
265
260
`
282
277
      range operator or as a graph difference.
283
278
 
284
279
    :param signature: show digital signature information
 
280
 
 
281
    :param match: Dictionary of list of search strings to use when filtering
 
282
      revisions. Keys can be 'message', 'author', 'committer', 'bugs' or
 
283
      the empty string to match any of the preceding properties.
 
284
 
 
285
    :param omit_merges: If True, commits with more than one parent are
 
286
      omitted.
 
287
 
285
288
    """
 
289
    # Take care of old style message_search parameter
 
290
    if message_search:
 
291
        if match:
 
292
            if 'message' in match:
 
293
                match['message'].append(message_search)
 
294
            else:
 
295
                match['message'] = [message_search]
 
296
        else:
 
297
            match={ 'message': [message_search] }
286
298
    return {
287
299
        'direction': direction,
288
300
        'specific_fileids': specific_fileids,
289
301
        'start_revision': start_revision,
290
302
        'end_revision': end_revision,
291
303
        'limit': limit,
292
 
        'message_search': message_search,
293
304
        'levels': levels,
294
305
        'generate_tags': generate_tags,
295
306
        'delta_type': delta_type,
296
307
        'diff_type': diff_type,
297
308
        'exclude_common_ancestry': exclude_common_ancestry,
298
309
        'signature': signature,
 
310
        'match': match,
 
311
        'omit_merges': omit_merges,
299
312
        # Add 'private' attributes for features that may be deprecated
300
313
        '_match_using_deltas': _match_using_deltas,
301
314
    }
311
324
 
312
325
def format_signature_validity(rev_id, repo):
313
326
    """get the signature validity
314
 
    
 
327
 
315
328
    :param rev_id: revision id to validate
316
329
    :param repo: repository of revision
317
330
    :return: human readable string to print to log
319
332
    from bzrlib import gpg
320
333
 
321
334
    gpg_strategy = gpg.GPGStrategy(None)
322
 
    result = repo.verify_revision(rev_id, gpg_strategy)
 
335
    result = repo.verify_revision_signature(rev_id, gpg_strategy)
323
336
    if result[0] == gpg.SIGNATURE_VALID:
324
 
        return "valid signature from {0}".format(result[1])
 
337
        return u"valid signature from {0}".format(result[1])
325
338
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
326
339
        return "unknown key {0}".format(result[1])
327
340
    if result[0] == gpg.SIGNATURE_NOT_VALID:
380
393
        # Tweak the LogRequest based on what the LogFormatter can handle.
381
394
        # (There's no point generating stuff if the formatter can't display it.)
382
395
        rqst = self.rqst
383
 
        rqst['levels'] = lf.get_levels()
 
396
        if rqst['levels'] is None or lf.get_levels() > rqst['levels']:
 
397
            # user didn't specify levels, use whatever the LF can handle:
 
398
            rqst['levels'] = lf.get_levels()
 
399
 
384
400
        if not getattr(lf, 'supports_tags', False):
385
401
            rqst['generate_tags'] = False
386
402
        if not getattr(lf, 'supports_delta', False):
398
414
 
399
415
    def _generator_factory(self, branch, rqst):
400
416
        """Make the LogGenerator object to use.
401
 
        
 
417
 
402
418
        Subclasses may wish to override this.
403
419
        """
404
420
        return _DefaultLogGenerator(branch, rqst)
429
445
        limit = rqst.get('limit')
430
446
        diff_type = rqst.get('diff_type')
431
447
        show_signature = rqst.get('signature')
 
448
        omit_merges = rqst.get('omit_merges')
432
449
        log_count = 0
433
450
        revision_iterator = self._create_log_revision_iterator()
434
451
        for revs in revision_iterator:
436
453
                # 0 levels means show everything; merge_depth counts from 0
437
454
                if levels != 0 and merge_depth >= levels:
438
455
                    continue
 
456
                if omit_merges and len(rev.parent_ids) > 1:
 
457
                    continue
439
458
                if diff_type is None:
440
459
                    diff = None
441
460
                else:
507
526
 
508
527
        # Apply the other filters
509
528
        return make_log_rev_iterator(self.branch, view_revisions,
510
 
            rqst.get('delta_type'), rqst.get('message_search'),
 
529
            rqst.get('delta_type'), rqst.get('match'),
511
530
            file_ids=rqst.get('specific_fileids'),
512
531
            direction=rqst.get('direction'))
513
532
 
526
545
            rqst.get('specific_fileids')[0], view_revisions,
527
546
            include_merges=rqst.get('levels') != 1)
528
547
        return make_log_rev_iterator(self.branch, view_revisions,
529
 
            rqst.get('delta_type'), rqst.get('message_search'))
 
548
            rqst.get('delta_type'), rqst.get('match'))
530
549
 
531
550
 
532
551
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
540
559
             a list of the same tuples.
541
560
    """
542
561
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
543
 
        raise errors.BzrCommandError(
544
 
            '--exclude-common-ancestry requires two different revisions')
 
562
        raise errors.BzrCommandError(gettext(
 
563
            '--exclude-common-ancestry requires two different revisions'))
545
564
    if direction not in ('reverse', 'forward'):
546
 
        raise ValueError('invalid direction %r' % direction)
 
565
        raise ValueError(gettext('invalid direction %r') % direction)
547
566
    br_revno, br_rev_id = branch.last_revision_info()
548
567
    if br_revno == 0:
549
568
        return []
552
571
        and (not generate_merge_revisions
553
572
             or not _has_merges(branch, end_rev_id))):
554
573
        # 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)))
 
574
        return  _generate_one_revision(branch, end_rev_id, br_rev_id,
 
575
                                       br_revno)
 
576
    if not generate_merge_revisions:
 
577
        try:
 
578
            # If we only want to see linear revisions, we can iterate ...
 
579
            iter_revs = _linear_view_revisions(
 
580
                branch, start_rev_id, end_rev_id,
 
581
                exclude_common_ancestry=exclude_common_ancestry)
 
582
            # If a start limit was given and it's not obviously an
 
583
            # ancestor of the end limit, check it before outputting anything
 
584
            if (direction == 'forward'
 
585
                or (start_rev_id and not _is_obvious_ancestor(
 
586
                        branch, start_rev_id, end_rev_id))):
 
587
                    iter_revs = list(iter_revs)
 
588
            if direction == 'forward':
 
589
                iter_revs = reversed(iter_revs)
 
590
            return iter_revs
 
591
        except _StartNotLinearAncestor:
 
592
            # Switch to the slower implementation that may be able to find a
 
593
            # non-obvious ancestor out of the left-hand history.
 
594
            pass
 
595
    iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
 
596
                                        direction, delayed_graph_generation,
 
597
                                        exclude_common_ancestry)
 
598
    if direction == 'forward':
 
599
        iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
569
600
    return iter_revs
570
601
 
571
602
 
578
609
        return [(rev_id, revno_str, 0)]
579
610
 
580
611
 
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
612
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
599
613
                            delayed_graph_generation,
600
614
                            exclude_common_ancestry=False):
636
650
        except _StartNotLinearAncestor:
637
651
            # A merge was never detected so the lower revision limit can't
638
652
            # be nested down somewhere
639
 
            raise errors.BzrCommandError('Start revision not found in'
640
 
                ' history of end revision.')
 
653
            raise errors.BzrCommandError(gettext('Start revision not found in'
 
654
                ' history of end revision.'))
641
655
 
642
656
    # We exit the loop above because we encounter a revision with merges, from
643
657
    # this revision, we need to switch to _graph_view_revisions.
783
797
            yield rev_id, '.'.join(map(str, revno)), merge_depth
784
798
 
785
799
 
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
800
def _rebase_merge_depth(view_revisions):
806
801
    """Adjust depths upwards so the top level is 0."""
807
802
    # If either the first or last revision have a merge_depth of 0, we're done
851
846
    return log_rev_iterator
852
847
 
853
848
 
854
 
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
 
849
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
855
850
    """Create a filtered iterator of log_rev_iterator matching on a regex.
856
851
 
857
852
    :param branch: The branch being logged.
858
853
    :param generate_delta: Whether to generate a delta for each revision.
859
 
    :param search: A user text search string.
 
854
    :param match: A dictionary with properties as keys and lists of strings
 
855
        as values. To match, a revision may match any of the supplied strings
 
856
        within a single property but must match at least one string for each
 
857
        property.
860
858
    :param log_rev_iterator: An input iterator containing all revisions that
861
859
        could be displayed, in lists.
862
860
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
863
861
        delta).
864
862
    """
865
 
    if search is None:
 
863
    if match is None:
866
864
        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):
 
865
    searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
 
866
                for (k,v) in match.iteritems()]
 
867
    return _filter_re(searchRE, log_rev_iterator)
 
868
 
 
869
 
 
870
def _filter_re(searchRE, log_rev_iterator):
872
871
    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
 
 
 
872
        new_revs = [rev for rev in revs if _match_filter(searchRE, rev[1])]
 
873
        if new_revs:
 
874
            yield new_revs
 
875
 
 
876
def _match_filter(searchRE, rev):
 
877
    strings = {
 
878
               'message': (rev.message,),
 
879
               'committer': (rev.committer,),
 
880
               'author': (rev.get_apparent_authors()),
 
881
               'bugs': list(rev.iter_bugs())
 
882
               }
 
883
    strings[''] = [item for inner_list in strings.itervalues()
 
884
                   for item in inner_list]
 
885
    for (k,v) in searchRE:
 
886
        if k in strings and not _match_any_filter(strings[k], v):
 
887
            return False
 
888
    return True
 
889
 
 
890
def _match_any_filter(strings, res):
 
891
    return any([filter(None, map(re.search, strings)) for re in res])
879
892
 
880
893
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
881
894
    fileids=None, direction='reverse'):
954
967
 
955
968
def _update_fileids(delta, fileids, stop_on):
956
969
    """Update the set of file-ids to search based on file lifecycle events.
957
 
    
 
970
 
958
971
    :param fileids: a set of fileids to update
959
972
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
960
973
      fileids set once their add or remove entry is detected respectively
1001
1014
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
1002
1015
        delta).
1003
1016
    """
1004
 
    repository = branch.repository
1005
1017
    num = 9
1006
1018
    for batch in log_rev_iterator:
1007
1019
        batch = iter(batch)
1056
1068
    if branch_revno != 0:
1057
1069
        if (start_rev_id == _mod_revision.NULL_REVISION
1058
1070
            or end_rev_id == _mod_revision.NULL_REVISION):
1059
 
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
1071
            raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1060
1072
        if start_revno > end_revno:
1061
 
            raise errors.BzrCommandError("Start revision must be older than "
1062
 
                                         "the end revision.")
 
1073
            raise errors.BzrCommandError(gettext("Start revision must be "
 
1074
                                         "older than the end revision."))
1063
1075
    return (start_rev_id, end_rev_id)
1064
1076
 
1065
1077
 
1114
1126
 
1115
1127
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1116
1128
        or (end_rev_id == _mod_revision.NULL_REVISION)):
1117
 
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
1129
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1118
1130
    if start_revno > end_revno:
1119
 
        raise errors.BzrCommandError("Start revision must be older than "
1120
 
                                     "the end revision.")
 
1131
        raise errors.BzrCommandError(gettext("Start revision must be older "
 
1132
                                     "than the end revision."))
1121
1133
 
1122
1134
    if end_revno < start_revno:
1123
1135
        return None, None, None, None
1146
1158
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1147
1159
 
1148
1160
 
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
1161
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1194
1162
    include_merges=True):
1195
1163
    r"""Return the list of revision ids which touch a given file id.
1274
1242
    return result
1275
1243
 
1276
1244
 
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
1245
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1319
1246
    """Reverse revisions by depth.
1320
1247
 
1417
1344
        """Create a LogFormatter.
1418
1345
 
1419
1346
        :param to_file: the file to output to
1420
 
        :param to_exact_file: if set, gives an output stream to which 
 
1347
        :param to_exact_file: if set, gives an output stream to which
1421
1348
             non-Unicode diffs are written.
1422
1349
        :param show_ids: if True, revision-ids are to be displayed
1423
1350
        :param show_timezone: the timezone to use
1474
1401
            if advice_sep:
1475
1402
                self.to_file.write(advice_sep)
1476
1403
            self.to_file.write(
1477
 
                "Use --include-merges or -n0 to see merged revisions.\n")
 
1404
                "Use --include-merged or -n0 to see merged revisions.\n")
1478
1405
 
1479
1406
    def get_advice_separator(self):
1480
1407
        """Get the text separating the log from the closing advice."""
1659
1586
        if revision.delta is not None:
1660
1587
            # Use the standard status output to display changes
1661
1588
            from bzrlib.delta import report_delta
1662
 
            report_delta(to_file, revision.delta, short_status=False, 
 
1589
            report_delta(to_file, revision.delta, short_status=False,
1663
1590
                         show_ids=self.show_ids, indent=indent)
1664
1591
        if revision.diff is not None:
1665
1592
            to_file.write(indent + 'diff:\n')
1731
1658
        if revision.delta is not None:
1732
1659
            # Use the standard status output to display changes
1733
1660
            from bzrlib.delta import report_delta
1734
 
            report_delta(to_file, revision.delta, 
1735
 
                         short_status=self.delta_format==1, 
 
1661
            report_delta(to_file, revision.delta,
 
1662
                         short_status=self.delta_format==1,
1736
1663
                         show_ids=self.show_ids, indent=indent + offset)
1737
1664
        if revision.diff is not None:
1738
1665
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1858
1785
        return self.get(name)(*args, **kwargs)
1859
1786
 
1860
1787
    def get_default(self, branch):
1861
 
        return self.get(branch.get_config().log_format())
 
1788
        c = branch.get_config_stack()
 
1789
        return self.get(c.get('log_format'))
1862
1790
 
1863
1791
 
1864
1792
log_formatter_registry = LogFormatterRegistry()
1865
1793
 
1866
1794
 
1867
1795
log_formatter_registry.register('short', ShortLogFormatter,
1868
 
                                'Moderately short log format')
 
1796
                                'Moderately short log format.')
1869
1797
log_formatter_registry.register('long', LongLogFormatter,
1870
 
                                'Detailed log format')
 
1798
                                'Detailed log format.')
1871
1799
log_formatter_registry.register('line', LineLogFormatter,
1872
 
                                'Log format with one line per revision')
 
1800
                                'Log format with one line per revision.')
1873
1801
log_formatter_registry.register('gnu-changelog', GnuChangelogLogFormatter,
1874
 
                                'Format used by GNU ChangeLog files')
 
1802
                                'Format used by GNU ChangeLog files.')
1875
1803
 
1876
1804
 
1877
1805
def register_formatter(name, formatter):
1887
1815
    try:
1888
1816
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1889
1817
    except KeyError:
1890
 
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
 
1818
        raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
1891
1819
 
1892
1820
 
1893
1821
def author_list_all(rev):
1918
1846
                              'The committer')
1919
1847
 
1920
1848
 
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
1849
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1928
1850
                           log_format='long'):
1929
1851
    """Show the change in revision history comparing the old revision history to the new one.
2092
2014
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2093
2015
      branch will be read-locked.
2094
2016
    """
2095
 
    from builtins import _get_revision_range
2096
 
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
 
2017
    from bzrlib.builtins import _get_revision_range
 
2018
    tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
 
2019
        file_list[0])
2097
2020
    add_cleanup(b.lock_read().unlock)
2098
2021
    # XXX: It's damn messy converting a list of paths to relative paths when
2099
2022
    # those paths might be deleted ones, they might be on a case-insensitive
2188
2111
                          len(row) > 1 and row[1] == 'fixed']
2189
2112
 
2190
2113
        if fixed_bug_urls:
2191
 
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
 
2114
            return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
 
2115
                    ' '.join(fixed_bug_urls)}
2192
2116
    return {}
2193
2117
 
2194
2118
properties_handler_registry.register('bugs_properties_handler',