~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Jelmer Vernooij
  • Date: 2011-06-16 11:58:04 UTC
  • mto: This revision was merged to the branch mainline in revision 5987.
  • Revision ID: jelmer@samba.org-20110616115804-7tnqon61emrbdoxm
RemoveĀ unusedĀ Tree._get_ancestors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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,
75
75
    revisionspec,
76
76
    tsort,
77
77
    )
78
 
from bzrlib.i18n import gettext, ngettext
79
78
""")
80
79
 
81
80
from bzrlib import (
82
 
    lazy_regex,
83
81
    registry,
84
82
    )
85
83
from bzrlib.osutils import (
89
87
    get_terminal_encoding,
90
88
    terminal_width,
91
89
    )
 
90
from bzrlib.symbol_versioning import (
 
91
    deprecated_function,
 
92
    deprecated_in,
 
93
    )
92
94
 
93
95
 
94
96
def find_touching_revisions(branch, file_id):
105
107
    last_ie = None
106
108
    last_path = None
107
109
    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):
 
110
    for revision_id in branch.revision_history():
112
111
        this_inv = branch.repository.get_inventory(revision_id)
113
 
        if this_inv.has_id(file_id):
 
112
        if file_id in this_inv:
114
113
            this_ie = this_inv[file_id]
115
114
            this_path = this_inv.id2path(file_id)
116
115
        else:
156
155
             end_revision=None,
157
156
             search=None,
158
157
             limit=None,
159
 
             show_diff=False,
160
 
             match=None):
 
158
             show_diff=False):
161
159
    """Write out human-readable log of commits to this branch.
162
160
 
163
161
    This function is being retained for backwards compatibility but
186
184
        if None or 0.
187
185
 
188
186
    :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
187
    """
193
188
    # Convert old-style parameters to new-style parameters
194
189
    if specific_fileid is not None:
218
213
    Logger(branch, rqst).show(lf)
219
214
 
220
215
 
221
 
# Note: This needs to be kept in sync with the defaults in
 
216
# Note: This needs to be kept this in sync with the defaults in
222
217
# make_log_request_dict() below
223
218
_DEFAULT_REQUEST_PARAMS = {
224
219
    'direction': 'reverse',
225
 
    'levels': None,
 
220
    'levels': 1,
226
221
    'generate_tags': True,
227
222
    'exclude_common_ancestry': False,
228
223
    '_match_using_deltas': True,
231
226
 
232
227
def make_log_request_dict(direction='reverse', specific_fileids=None,
233
228
                          start_revision=None, end_revision=None, limit=None,
234
 
                          message_search=None, levels=None, generate_tags=True,
 
229
                          message_search=None, levels=1, generate_tags=True,
235
230
                          delta_type=None,
236
231
                          diff_type=None, _match_using_deltas=True,
237
 
                          exclude_common_ancestry=False, match=None,
238
 
                          signature=False, omit_merges=False,
 
232
                          exclude_common_ancestry=False,
239
233
                          ):
240
234
    """Convenience function for making a logging request dictionary.
241
235
 
262
256
      matching commit messages
263
257
 
264
258
    :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.
 
259
      generate; 1 for just the mainline; 0 for all levels.
267
260
 
268
261
    :param generate_tags: If True, include tags for matched revisions.
269
 
`
 
262
 
270
263
    :param delta_type: Either 'full', 'partial' or None.
271
264
      'full' means generate the complete delta - adds/deletes/modifies/etc;
272
265
      'partial' means filter the delta using specific_fileids;
284
277
 
285
278
    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
286
279
      range operator or as a graph difference.
287
 
 
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
 
 
297
280
    """
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
281
    return {
308
282
        'direction': direction,
309
283
        'specific_fileids': specific_fileids,
310
284
        'start_revision': start_revision,
311
285
        'end_revision': end_revision,
312
286
        'limit': limit,
 
287
        'message_search': message_search,
313
288
        'levels': levels,
314
289
        'generate_tags': generate_tags,
315
290
        'delta_type': delta_type,
316
291
        'diff_type': diff_type,
317
292
        'exclude_common_ancestry': exclude_common_ancestry,
318
 
        'signature': signature,
319
 
        'match': match,
320
 
        'omit_merges': omit_merges,
321
293
        # Add 'private' attributes for features that may be deprecated
322
294
        '_match_using_deltas': _match_using_deltas,
323
295
    }
331
303
    return result
332
304
 
333
305
 
334
 
def format_signature_validity(rev_id, repo):
335
 
    """get the signature validity
336
 
 
337
 
    :param rev_id: revision id to validate
338
 
    :param repo: repository of revision
339
 
    :return: human readable string to print to log
340
 
    """
341
 
    from bzrlib import gpg
342
 
 
343
 
    gpg_strategy = gpg.GPGStrategy(None)
344
 
    result = repo.verify_revision(rev_id, gpg_strategy)
345
 
    if result[0] == gpg.SIGNATURE_VALID:
346
 
        return "valid signature from {0}".format(result[1])
347
 
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
348
 
        return "unknown key {0}".format(result[1])
349
 
    if result[0] == gpg.SIGNATURE_NOT_VALID:
350
 
        return "invalid signature!"
351
 
    if result[0] == gpg.SIGNATURE_NOT_SIGNED:
352
 
        return "no signature"
353
 
 
354
 
 
355
306
class LogGenerator(object):
356
307
    """A generator of log revisions."""
357
308
 
402
353
        # Tweak the LogRequest based on what the LogFormatter can handle.
403
354
        # (There's no point generating stuff if the formatter can't display it.)
404
355
        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
 
 
 
356
        rqst['levels'] = lf.get_levels()
409
357
        if not getattr(lf, 'supports_tags', False):
410
358
            rqst['generate_tags'] = False
411
359
        if not getattr(lf, 'supports_delta', False):
412
360
            rqst['delta_type'] = None
413
361
        if not getattr(lf, 'supports_diff', False):
414
362
            rqst['diff_type'] = None
415
 
        if not getattr(lf, 'supports_signatures', False):
416
 
            rqst['signature'] = False
417
363
 
418
364
        # Find and print the interesting revisions
419
365
        generator = self._generator_factory(self.branch, rqst)
423
369
 
424
370
    def _generator_factory(self, branch, rqst):
425
371
        """Make the LogGenerator object to use.
426
 
 
 
372
        
427
373
        Subclasses may wish to override this.
428
374
        """
429
375
        return _DefaultLogGenerator(branch, rqst)
453
399
        levels = rqst.get('levels')
454
400
        limit = rqst.get('limit')
455
401
        diff_type = rqst.get('diff_type')
456
 
        show_signature = rqst.get('signature')
457
 
        omit_merges = rqst.get('omit_merges')
458
402
        log_count = 0
459
403
        revision_iterator = self._create_log_revision_iterator()
460
404
        for revs in revision_iterator:
462
406
                # 0 levels means show everything; merge_depth counts from 0
463
407
                if levels != 0 and merge_depth >= levels:
464
408
                    continue
465
 
                if omit_merges and len(rev.parent_ids) > 1:
466
 
                    continue
467
409
                if diff_type is None:
468
410
                    diff = None
469
411
                else:
470
412
                    diff = self._format_diff(rev, rev_id, diff_type)
471
 
                if show_signature:
472
 
                    signature = format_signature_validity(rev_id,
473
 
                                                self.branch.repository)
474
 
                else:
475
 
                    signature = None
476
413
                yield LogRevision(rev, revno, merge_depth, delta,
477
 
                    self.rev_tag_dict.get(rev_id), diff, signature)
 
414
                    self.rev_tag_dict.get(rev_id), diff)
478
415
                if limit:
479
416
                    log_count += 1
480
417
                    if log_count >= limit:
535
472
 
536
473
        # Apply the other filters
537
474
        return make_log_rev_iterator(self.branch, view_revisions,
538
 
            rqst.get('delta_type'), rqst.get('match'),
 
475
            rqst.get('delta_type'), rqst.get('message_search'),
539
476
            file_ids=rqst.get('specific_fileids'),
540
477
            direction=rqst.get('direction'))
541
478
 
554
491
            rqst.get('specific_fileids')[0], view_revisions,
555
492
            include_merges=rqst.get('levels') != 1)
556
493
        return make_log_rev_iterator(self.branch, view_revisions,
557
 
            rqst.get('delta_type'), rqst.get('match'))
 
494
            rqst.get('delta_type'), rqst.get('message_search'))
558
495
 
559
496
 
560
497
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
568
505
             a list of the same tuples.
569
506
    """
570
507
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
571
 
        raise errors.BzrCommandError(gettext(
572
 
            '--exclude-common-ancestry requires two different revisions'))
 
508
        raise errors.BzrCommandError(
 
509
            '--exclude-common-ancestry requires two different revisions')
573
510
    if direction not in ('reverse', 'forward'):
574
 
        raise ValueError(gettext('invalid direction %r') % direction)
 
511
        raise ValueError('invalid direction %r' % direction)
575
512
    br_revno, br_rev_id = branch.last_revision_info()
576
513
    if br_revno == 0:
577
514
        return []
618
555
        try:
619
556
            result = list(result)
620
557
        except _StartNotLinearAncestor:
621
 
            raise errors.BzrCommandError(gettext('Start revision not found in'
622
 
                ' left-hand history of end revision.'))
 
558
            raise errors.BzrCommandError('Start revision not found in'
 
559
                ' left-hand history of end revision.')
623
560
    return result
624
561
 
625
562
 
664
601
        except _StartNotLinearAncestor:
665
602
            # A merge was never detected so the lower revision limit can't
666
603
            # be nested down somewhere
667
 
            raise errors.BzrCommandError(gettext('Start revision not found in'
668
 
                ' history of end revision.'))
 
604
            raise errors.BzrCommandError('Start revision not found in'
 
605
                ' history of end revision.')
669
606
 
670
607
    # We exit the loop above because we encounter a revision with merges, from
671
608
    # this revision, we need to switch to _graph_view_revisions.
741
678
    """
742
679
    br_revno, br_rev_id = branch.last_revision_info()
743
680
    repo = branch.repository
744
 
    graph = repo.get_graph()
745
681
    if start_rev_id is None and end_rev_id is None:
746
682
        cur_revno = br_revno
747
 
        for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
748
 
            (_mod_revision.NULL_REVISION,)):
 
683
        for revision_id in repo.iter_reverse_revision_history(br_rev_id):
749
684
            yield revision_id, str(cur_revno), 0
750
685
            cur_revno -= 1
751
686
    else:
752
687
        if end_rev_id is None:
753
688
            end_rev_id = br_rev_id
754
689
        found_start = start_rev_id is None
755
 
        for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
756
 
                (_mod_revision.NULL_REVISION,)):
 
690
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
757
691
            revno_str = _compute_revno_str(branch, revision_id)
758
692
            if not found_start and revision_id == start_rev_id:
759
693
                if not exclude_common_ancestry:
811
745
            yield rev_id, '.'.join(map(str, revno)), merge_depth
812
746
 
813
747
 
 
748
@deprecated_function(deprecated_in((2, 2, 0)))
 
749
def calculate_view_revisions(branch, start_revision, end_revision, direction,
 
750
        specific_fileid, generate_merge_revisions):
 
751
    """Calculate the revisions to view.
 
752
 
 
753
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
 
754
             a list of the same tuples.
 
755
    """
 
756
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
 
757
        end_revision)
 
758
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
 
759
        direction, generate_merge_revisions or specific_fileid))
 
760
    if specific_fileid:
 
761
        view_revisions = _filter_revisions_touching_file_id(branch,
 
762
            specific_fileid, view_revisions,
 
763
            include_merges=generate_merge_revisions)
 
764
    return _rebase_merge_depth(view_revisions)
 
765
 
 
766
 
814
767
def _rebase_merge_depth(view_revisions):
815
768
    """Adjust depths upwards so the top level is 0."""
816
769
    # If either the first or last revision have a merge_depth of 0, we're done
860
813
    return log_rev_iterator
861
814
 
862
815
 
863
 
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
 
816
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
864
817
    """Create a filtered iterator of log_rev_iterator matching on a regex.
865
818
 
866
819
    :param branch: The branch being logged.
867
820
    :param generate_delta: Whether to generate a delta for each revision.
868
 
    :param match: A dictionary with properties as keys and lists of strings
869
 
        as values. To match, a revision may match any of the supplied strings
870
 
        within a single property but must match at least one string for each
871
 
        property.
 
821
    :param search: A user text search string.
872
822
    :param log_rev_iterator: An input iterator containing all revisions that
873
823
        could be displayed, in lists.
874
824
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
875
825
        delta).
876
826
    """
877
 
    if match is None:
 
827
    if search is None:
878
828
        return log_rev_iterator
879
 
    searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
880
 
                for (k,v) in match.iteritems()]
881
 
    return _filter_re(searchRE, log_rev_iterator)
882
 
 
883
 
 
884
 
def _filter_re(searchRE, log_rev_iterator):
 
829
    searchRE = re.compile(search, re.IGNORECASE)
 
830
    return _filter_message_re(searchRE, log_rev_iterator)
 
831
 
 
832
 
 
833
def _filter_message_re(searchRE, log_rev_iterator):
885
834
    for revs in log_rev_iterator:
886
 
        new_revs = [rev for rev in revs if _match_filter(searchRE, rev[1])]
887
 
        if new_revs:
888
 
            yield new_revs
889
 
 
890
 
def _match_filter(searchRE, rev):
891
 
    strings = {
892
 
               'message': (rev.message,),
893
 
               'committer': (rev.committer,),
894
 
               'author': (rev.get_apparent_authors()),
895
 
               'bugs': list(rev.iter_bugs())
896
 
               }
897
 
    strings[''] = [item for inner_list in strings.itervalues()
898
 
                   for item in inner_list]
899
 
    for (k,v) in searchRE:
900
 
        if k in strings and not _match_any_filter(strings[k], v):
901
 
            return False
902
 
    return True
903
 
 
904
 
def _match_any_filter(strings, res):
905
 
    return any([filter(None, map(re.search, strings)) for re in res])
 
835
        new_revs = []
 
836
        for (rev_id, revno, merge_depth), rev, delta in revs:
 
837
            if searchRE.search(rev.message):
 
838
                new_revs.append(((rev_id, revno, merge_depth), rev, delta))
 
839
        yield new_revs
 
840
 
906
841
 
907
842
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
908
843
    fileids=None, direction='reverse'):
981
916
 
982
917
def _update_fileids(delta, fileids, stop_on):
983
918
    """Update the set of file-ids to search based on file lifecycle events.
984
 
 
 
919
    
985
920
    :param fileids: a set of fileids to update
986
921
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
987
922
      fileids set once their add or remove entry is detected respectively
1028
963
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
1029
964
        delta).
1030
965
    """
 
966
    repository = branch.repository
1031
967
    num = 9
1032
968
    for batch in log_rev_iterator:
1033
969
        batch = iter(batch)
1082
1018
    if branch_revno != 0:
1083
1019
        if (start_rev_id == _mod_revision.NULL_REVISION
1084
1020
            or end_rev_id == _mod_revision.NULL_REVISION):
1085
 
            raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
 
1021
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
1086
1022
        if start_revno > end_revno:
1087
 
            raise errors.BzrCommandError(gettext("Start revision must be "
1088
 
                                         "older than the end revision."))
 
1023
            raise errors.BzrCommandError("Start revision must be older than "
 
1024
                                         "the end revision.")
1089
1025
    return (start_rev_id, end_rev_id)
1090
1026
 
1091
1027
 
1140
1076
 
1141
1077
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1142
1078
        or (end_rev_id == _mod_revision.NULL_REVISION)):
1143
 
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
 
1079
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
1144
1080
    if start_revno > end_revno:
1145
 
        raise errors.BzrCommandError(gettext("Start revision must be older "
1146
 
                                     "than the end revision."))
 
1081
        raise errors.BzrCommandError("Start revision must be older than "
 
1082
                                     "the end revision.")
1147
1083
 
1148
1084
    if end_revno < start_revno:
1149
1085
        return None, None, None, None
1150
1086
    cur_revno = branch_revno
1151
1087
    rev_nos = {}
1152
1088
    mainline_revs = []
1153
 
    graph = branch.repository.get_graph()
1154
 
    for revision_id in graph.iter_lefthand_ancestry(
1155
 
            branch_last_revision, (_mod_revision.NULL_REVISION,)):
 
1089
    for revision_id in branch.repository.iter_reverse_revision_history(
 
1090
                        branch_last_revision):
1156
1091
        if cur_revno < start_revno:
1157
1092
            # We have gone far enough, but we always add 1 more revision
1158
1093
            rev_nos[revision_id] = cur_revno
1172
1107
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1173
1108
 
1174
1109
 
 
1110
@deprecated_function(deprecated_in((2, 2, 0)))
 
1111
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
 
1112
    """Filter view_revisions based on revision ranges.
 
1113
 
 
1114
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
 
1115
            tuples to be filtered.
 
1116
 
 
1117
    :param start_rev_id: If not NONE specifies the first revision to be logged.
 
1118
            If NONE then all revisions up to the end_rev_id are logged.
 
1119
 
 
1120
    :param end_rev_id: If not NONE specifies the last revision to be logged.
 
1121
            If NONE then all revisions up to the end of the log are logged.
 
1122
 
 
1123
    :return: The filtered view_revisions.
 
1124
    """
 
1125
    if start_rev_id or end_rev_id:
 
1126
        revision_ids = [r for r, n, d in view_revisions]
 
1127
        if start_rev_id:
 
1128
            start_index = revision_ids.index(start_rev_id)
 
1129
        else:
 
1130
            start_index = 0
 
1131
        if start_rev_id == end_rev_id:
 
1132
            end_index = start_index
 
1133
        else:
 
1134
            if end_rev_id:
 
1135
                end_index = revision_ids.index(end_rev_id)
 
1136
            else:
 
1137
                end_index = len(view_revisions) - 1
 
1138
        # To include the revisions merged into the last revision,
 
1139
        # extend end_rev_id down to, but not including, the next rev
 
1140
        # with the same or lesser merge_depth
 
1141
        end_merge_depth = view_revisions[end_index][2]
 
1142
        try:
 
1143
            for index in xrange(end_index+1, len(view_revisions)+1):
 
1144
                if view_revisions[index][2] <= end_merge_depth:
 
1145
                    end_index = index - 1
 
1146
                    break
 
1147
        except IndexError:
 
1148
            # if the search falls off the end then log to the end as well
 
1149
            end_index = len(view_revisions) - 1
 
1150
        view_revisions = view_revisions[start_index:end_index+1]
 
1151
    return view_revisions
 
1152
 
 
1153
 
1175
1154
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1176
1155
    include_merges=True):
1177
1156
    r"""Return the list of revision ids which touch a given file id.
1256
1235
    return result
1257
1236
 
1258
1237
 
 
1238
@deprecated_function(deprecated_in((2, 2, 0)))
 
1239
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
 
1240
                       include_merges=True):
 
1241
    """Produce an iterator of revisions to show
 
1242
    :return: an iterator of (revision_id, revno, merge_depth)
 
1243
    (if there is no revno for a revision, None is supplied)
 
1244
    """
 
1245
    if not include_merges:
 
1246
        revision_ids = mainline_revs[1:]
 
1247
        if direction == 'reverse':
 
1248
            revision_ids.reverse()
 
1249
        for revision_id in revision_ids:
 
1250
            yield revision_id, str(rev_nos[revision_id]), 0
 
1251
        return
 
1252
    graph = branch.repository.get_graph()
 
1253
    # This asks for all mainline revisions, which means we only have to spider
 
1254
    # sideways, rather than depth history. That said, its still size-of-history
 
1255
    # and should be addressed.
 
1256
    # mainline_revisions always includes an extra revision at the beginning, so
 
1257
    # don't request it.
 
1258
    parent_map = dict(((key, value) for key, value in
 
1259
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
 
1260
    # filter out ghosts; merge_sort errors on ghosts.
 
1261
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
 
1262
    merge_sorted_revisions = tsort.merge_sort(
 
1263
        rev_graph,
 
1264
        mainline_revs[-1],
 
1265
        mainline_revs,
 
1266
        generate_revno=True)
 
1267
 
 
1268
    if direction == 'forward':
 
1269
        # forward means oldest first.
 
1270
        merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
 
1271
    elif direction != 'reverse':
 
1272
        raise ValueError('invalid direction %r' % direction)
 
1273
 
 
1274
    for (sequence, rev_id, merge_depth, revno, end_of_merge
 
1275
         ) in merge_sorted_revisions:
 
1276
        yield rev_id, '.'.join(map(str, revno)), merge_depth
 
1277
 
 
1278
 
1259
1279
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1260
1280
    """Reverse revisions by depth.
1261
1281
 
1296
1316
    """
1297
1317
 
1298
1318
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1299
 
                 tags=None, diff=None, signature=None):
 
1319
                 tags=None, diff=None):
1300
1320
        self.rev = rev
1301
1321
        if revno is None:
1302
1322
            self.revno = None
1306
1326
        self.delta = delta
1307
1327
        self.tags = tags
1308
1328
        self.diff = diff
1309
 
        self.signature = signature
1310
1329
 
1311
1330
 
1312
1331
class LogFormatter(object):
1339
1358
    - supports_diff must be True if this log formatter supports diffs.
1340
1359
      Otherwise the diff attribute may not be populated.
1341
1360
 
1342
 
    - supports_signatures must be True if this log formatter supports GPG
1343
 
      signatures.
1344
 
 
1345
1361
    Plugins can register functions to show custom revision properties using
1346
1362
    the properties_handler_registry. The registered function
1347
1363
    must respect the following interface description::
1358
1374
        """Create a LogFormatter.
1359
1375
 
1360
1376
        :param to_file: the file to output to
1361
 
        :param to_exact_file: if set, gives an output stream to which
 
1377
        :param to_exact_file: if set, gives an output stream to which 
1362
1378
             non-Unicode diffs are written.
1363
1379
        :param show_ids: if True, revision-ids are to be displayed
1364
1380
        :param show_timezone: the timezone to use
1415
1431
            if advice_sep:
1416
1432
                self.to_file.write(advice_sep)
1417
1433
            self.to_file.write(
1418
 
                "Use --include-merged or -n0 to see merged revisions.\n")
 
1434
                "Use --include-merges or -n0 to see merged revisions.\n")
1419
1435
 
1420
1436
    def get_advice_separator(self):
1421
1437
        """Get the text separating the log from the closing advice."""
1538
1554
    supports_delta = True
1539
1555
    supports_tags = True
1540
1556
    supports_diff = True
1541
 
    supports_signatures = True
1542
1557
 
1543
1558
    def __init__(self, *args, **kwargs):
1544
1559
        super(LongLogFormatter, self).__init__(*args, **kwargs)
1583
1598
 
1584
1599
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1585
1600
 
1586
 
        if revision.signature is not None:
1587
 
            lines.append('signature: ' + revision.signature)
1588
 
 
1589
1601
        lines.append('message:')
1590
1602
        if not revision.rev.message:
1591
1603
            lines.append('  (no message)')
1600
1612
        if revision.delta is not None:
1601
1613
            # Use the standard status output to display changes
1602
1614
            from bzrlib.delta import report_delta
1603
 
            report_delta(to_file, revision.delta, short_status=False,
 
1615
            report_delta(to_file, revision.delta, short_status=False, 
1604
1616
                         show_ids=self.show_ids, indent=indent)
1605
1617
        if revision.diff is not None:
1606
1618
            to_file.write(indent + 'diff:\n')
1672
1684
        if revision.delta is not None:
1673
1685
            # Use the standard status output to display changes
1674
1686
            from bzrlib.delta import report_delta
1675
 
            report_delta(to_file, revision.delta,
1676
 
                         short_status=self.delta_format==1,
 
1687
            report_delta(to_file, revision.delta, 
 
1688
                         short_status=self.delta_format==1, 
1677
1689
                         show_ids=self.show_ids, indent=indent + offset)
1678
1690
        if revision.diff is not None:
1679
1691
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1799
1811
        return self.get(name)(*args, **kwargs)
1800
1812
 
1801
1813
    def get_default(self, branch):
1802
 
        c = branch.get_config_stack()
1803
 
        return self.get(c.get('log_format'))
 
1814
        return self.get(branch.get_config().log_format())
1804
1815
 
1805
1816
 
1806
1817
log_formatter_registry = LogFormatterRegistry()
1829
1840
    try:
1830
1841
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1831
1842
    except KeyError:
1832
 
        raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
 
1843
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1833
1844
 
1834
1845
 
1835
1846
def author_list_all(rev):
1860
1871
                              'The committer')
1861
1872
 
1862
1873
 
 
1874
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
 
1875
    # deprecated; for compatibility
 
1876
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
 
1877
    lf.show(revno, rev, delta)
 
1878
 
 
1879
 
1863
1880
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1864
1881
                           log_format='long'):
1865
1882
    """Show the change in revision history comparing the old revision history to the new one.
1928
1945
    old_revisions = set()
1929
1946
    new_history = []
1930
1947
    new_revisions = set()
1931
 
    graph = repository.get_graph()
1932
 
    new_iter = graph.iter_lefthand_ancestry(new_revision_id)
1933
 
    old_iter = graph.iter_lefthand_ancestry(old_revision_id)
 
1948
    new_iter = repository.iter_reverse_revision_history(new_revision_id)
 
1949
    old_iter = repository.iter_reverse_revision_history(old_revision_id)
1934
1950
    stop_revision = None
1935
1951
    do_old = True
1936
1952
    do_new = True
2029
2045
      branch will be read-locked.
2030
2046
    """
2031
2047
    from builtins import _get_revision_range
2032
 
    tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2033
 
        file_list[0])
 
2048
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2034
2049
    add_cleanup(b.lock_read().unlock)
2035
2050
    # XXX: It's damn messy converting a list of paths to relative paths when
2036
2051
    # those paths might be deleted ones, they might be on a case-insensitive
2125
2140
                          len(row) > 1 and row[1] == 'fixed']
2126
2141
 
2127
2142
        if fixed_bug_urls:
2128
 
            return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
2129
 
                    ' '.join(fixed_bug_urls)}
 
2143
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
2130
2144
    return {}
2131
2145
 
2132
2146
properties_handler_registry.register('bugs_properties_handler',