~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-06-30 18:28:17 UTC
  • mfrom: (5967.10.2 test-cat)
  • Revision ID: pqm@pqm.ubuntu.com-20110630182817-83a5q9r9rxfkdn8r
(mbp) don't use subprocesses for testing cat (Martin Pool)

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
 
 
18
 
17
19
"""Code to show logs of changes.
18
20
 
19
21
Various flavors of log can be produced:
47
49
all the changes since the previous revision that touched hello.c.
48
50
"""
49
51
 
50
 
from __future__ import absolute_import
51
 
 
52
52
import codecs
53
53
from cStringIO import StringIO
54
54
from itertools import (
65
65
lazy_import(globals(), """
66
66
 
67
67
from bzrlib import (
 
68
    bzrdir,
68
69
    config,
69
 
    controldir,
70
70
    diff,
71
71
    errors,
72
72
    foreign,
74
74
    revision as _mod_revision,
75
75
    revisionspec,
76
76
    tsort,
 
77
    i18n,
77
78
    )
78
 
from bzrlib.i18n import gettext, ngettext
79
79
""")
80
80
 
81
81
from bzrlib import (
89
89
    get_terminal_encoding,
90
90
    terminal_width,
91
91
    )
 
92
from bzrlib.symbol_versioning import (
 
93
    deprecated_function,
 
94
    deprecated_in,
 
95
    )
92
96
 
93
97
 
94
98
def find_touching_revisions(branch, file_id):
105
109
    last_ie = None
106
110
    last_path = None
107
111
    revno = 1
108
 
    graph = branch.repository.get_graph()
109
 
    history = list(graph.iter_lefthand_ancestry(branch.last_revision(),
110
 
        [_mod_revision.NULL_REVISION]))
111
 
    for revision_id in reversed(history):
 
112
    for revision_id in branch.revision_history():
112
113
        this_inv = branch.repository.get_inventory(revision_id)
113
114
        if this_inv.has_id(file_id):
114
115
            this_ie = this_inv[file_id]
138
139
        revno += 1
139
140
 
140
141
 
 
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
 
141
151
def show_log(branch,
142
152
             lf,
143
153
             specific_fileid=None,
147
157
             end_revision=None,
148
158
             search=None,
149
159
             limit=None,
150
 
             show_diff=False,
151
 
             match=None):
 
160
             show_diff=False):
152
161
    """Write out human-readable log of commits to this branch.
153
162
 
154
163
    This function is being retained for backwards compatibility but
177
186
        if None or 0.
178
187
 
179
188
    :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.
183
189
    """
184
190
    # Convert old-style parameters to new-style parameters
185
191
    if specific_fileid is not None:
209
215
    Logger(branch, rqst).show(lf)
210
216
 
211
217
 
212
 
# Note: This needs to be kept in sync with the defaults in
 
218
# Note: This needs to be kept this in sync with the defaults in
213
219
# make_log_request_dict() below
214
220
_DEFAULT_REQUEST_PARAMS = {
215
221
    'direction': 'reverse',
216
 
    'levels': None,
 
222
    'levels': 1,
217
223
    'generate_tags': True,
218
224
    'exclude_common_ancestry': False,
219
225
    '_match_using_deltas': True,
222
228
 
223
229
def make_log_request_dict(direction='reverse', specific_fileids=None,
224
230
                          start_revision=None, end_revision=None, limit=None,
225
 
                          message_search=None, levels=None, generate_tags=True,
 
231
                          message_search=None, levels=1, generate_tags=True,
226
232
                          delta_type=None,
227
233
                          diff_type=None, _match_using_deltas=True,
228
 
                          exclude_common_ancestry=False, match=None,
229
 
                          signature=False, omit_merges=False,
 
234
                          exclude_common_ancestry=False,
 
235
                          signature=False,
230
236
                          ):
231
237
    """Convenience function for making a logging request dictionary.
232
238
 
253
259
      matching commit messages
254
260
 
255
261
    :param levels: the number of levels of revisions to
256
 
      generate; 1 for just the mainline; 0 for all levels, or None for
257
 
      a sensible default.
 
262
      generate; 1 for just the mainline; 0 for all levels.
258
263
 
259
264
    :param generate_tags: If True, include tags for matched revisions.
260
265
`
277
282
      range operator or as a graph difference.
278
283
 
279
284
    :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
 
 
288
285
    """
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] }
298
286
    return {
299
287
        'direction': direction,
300
288
        'specific_fileids': specific_fileids,
301
289
        'start_revision': start_revision,
302
290
        'end_revision': end_revision,
303
291
        'limit': limit,
 
292
        'message_search': message_search,
304
293
        'levels': levels,
305
294
        'generate_tags': generate_tags,
306
295
        'delta_type': delta_type,
307
296
        'diff_type': diff_type,
308
297
        'exclude_common_ancestry': exclude_common_ancestry,
309
298
        'signature': signature,
310
 
        'match': match,
311
 
        'omit_merges': omit_merges,
312
299
        # Add 'private' attributes for features that may be deprecated
313
300
        '_match_using_deltas': _match_using_deltas,
314
301
    }
324
311
 
325
312
def format_signature_validity(rev_id, repo):
326
313
    """get the signature validity
327
 
 
 
314
    
328
315
    :param rev_id: revision id to validate
329
316
    :param repo: repository of revision
330
317
    :return: human readable string to print to log
332
319
    from bzrlib import gpg
333
320
 
334
321
    gpg_strategy = gpg.GPGStrategy(None)
335
 
    result = repo.verify_revision_signature(rev_id, gpg_strategy)
 
322
    result = repo.verify_revision(rev_id, gpg_strategy)
336
323
    if result[0] == gpg.SIGNATURE_VALID:
337
 
        return u"valid signature from {0}".format(result[1])
 
324
        return "valid signature from {0}".format(result[1])
338
325
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
339
326
        return "unknown key {0}".format(result[1])
340
327
    if result[0] == gpg.SIGNATURE_NOT_VALID:
393
380
        # Tweak the LogRequest based on what the LogFormatter can handle.
394
381
        # (There's no point generating stuff if the formatter can't display it.)
395
382
        rqst = self.rqst
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
 
 
 
383
        rqst['levels'] = lf.get_levels()
400
384
        if not getattr(lf, 'supports_tags', False):
401
385
            rqst['generate_tags'] = False
402
386
        if not getattr(lf, 'supports_delta', False):
414
398
 
415
399
    def _generator_factory(self, branch, rqst):
416
400
        """Make the LogGenerator object to use.
417
 
 
 
401
        
418
402
        Subclasses may wish to override this.
419
403
        """
420
404
        return _DefaultLogGenerator(branch, rqst)
445
429
        limit = rqst.get('limit')
446
430
        diff_type = rqst.get('diff_type')
447
431
        show_signature = rqst.get('signature')
448
 
        omit_merges = rqst.get('omit_merges')
449
432
        log_count = 0
450
433
        revision_iterator = self._create_log_revision_iterator()
451
434
        for revs in revision_iterator:
453
436
                # 0 levels means show everything; merge_depth counts from 0
454
437
                if levels != 0 and merge_depth >= levels:
455
438
                    continue
456
 
                if omit_merges and len(rev.parent_ids) > 1:
457
 
                    continue
458
439
                if diff_type is None:
459
440
                    diff = None
460
441
                else:
526
507
 
527
508
        # Apply the other filters
528
509
        return make_log_rev_iterator(self.branch, view_revisions,
529
 
            rqst.get('delta_type'), rqst.get('match'),
 
510
            rqst.get('delta_type'), rqst.get('message_search'),
530
511
            file_ids=rqst.get('specific_fileids'),
531
512
            direction=rqst.get('direction'))
532
513
 
545
526
            rqst.get('specific_fileids')[0], view_revisions,
546
527
            include_merges=rqst.get('levels') != 1)
547
528
        return make_log_rev_iterator(self.branch, view_revisions,
548
 
            rqst.get('delta_type'), rqst.get('match'))
 
529
            rqst.get('delta_type'), rqst.get('message_search'))
549
530
 
550
531
 
551
532
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
559
540
             a list of the same tuples.
560
541
    """
561
542
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
562
 
        raise errors.BzrCommandError(gettext(
563
 
            '--exclude-common-ancestry requires two different revisions'))
 
543
        raise errors.BzrCommandError(
 
544
            '--exclude-common-ancestry requires two different revisions')
564
545
    if direction not in ('reverse', 'forward'):
565
 
        raise ValueError(gettext('invalid direction %r') % direction)
 
546
        raise ValueError('invalid direction %r' % direction)
566
547
    br_revno, br_rev_id = branch.last_revision_info()
567
548
    if br_revno == 0:
568
549
        return []
571
552
        and (not generate_merge_revisions
572
553
             or not _has_merges(branch, end_rev_id))):
573
554
        # If a single revision is requested, check we can handle it
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)))
 
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)))
600
569
    return iter_revs
601
570
 
602
571
 
609
578
        return [(rev_id, revno_str, 0)]
610
579
 
611
580
 
 
581
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
 
582
                             exclude_common_ancestry=False):
 
583
    result = _linear_view_revisions(
 
584
        branch, start_rev_id, end_rev_id,
 
585
        exclude_common_ancestry=exclude_common_ancestry)
 
586
    # If a start limit was given and it's not obviously an
 
587
    # ancestor of the end limit, check it before outputting anything
 
588
    if direction == 'forward' or (start_rev_id
 
589
        and not _is_obvious_ancestor(branch, start_rev_id, end_rev_id)):
 
590
        try:
 
591
            result = list(result)
 
592
        except _StartNotLinearAncestor:
 
593
            raise errors.BzrCommandError('Start revision not found in'
 
594
                ' left-hand history of end revision.')
 
595
    return result
 
596
 
 
597
 
612
598
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
613
599
                            delayed_graph_generation,
614
600
                            exclude_common_ancestry=False):
650
636
        except _StartNotLinearAncestor:
651
637
            # A merge was never detected so the lower revision limit can't
652
638
            # be nested down somewhere
653
 
            raise errors.BzrCommandError(gettext('Start revision not found in'
654
 
                ' history of end revision.'))
 
639
            raise errors.BzrCommandError('Start revision not found in'
 
640
                ' history of end revision.')
655
641
 
656
642
    # We exit the loop above because we encounter a revision with merges, from
657
643
    # this revision, we need to switch to _graph_view_revisions.
797
783
            yield rev_id, '.'.join(map(str, revno)), merge_depth
798
784
 
799
785
 
 
786
@deprecated_function(deprecated_in((2, 2, 0)))
 
787
def calculate_view_revisions(branch, start_revision, end_revision, direction,
 
788
        specific_fileid, generate_merge_revisions):
 
789
    """Calculate the revisions to view.
 
790
 
 
791
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
 
792
             a list of the same tuples.
 
793
    """
 
794
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
 
795
        end_revision)
 
796
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
 
797
        direction, generate_merge_revisions or specific_fileid))
 
798
    if specific_fileid:
 
799
        view_revisions = _filter_revisions_touching_file_id(branch,
 
800
            specific_fileid, view_revisions,
 
801
            include_merges=generate_merge_revisions)
 
802
    return _rebase_merge_depth(view_revisions)
 
803
 
 
804
 
800
805
def _rebase_merge_depth(view_revisions):
801
806
    """Adjust depths upwards so the top level is 0."""
802
807
    # If either the first or last revision have a merge_depth of 0, we're done
846
851
    return log_rev_iterator
847
852
 
848
853
 
849
 
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
 
854
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
850
855
    """Create a filtered iterator of log_rev_iterator matching on a regex.
851
856
 
852
857
    :param branch: The branch being logged.
853
858
    :param generate_delta: Whether to generate a delta for each revision.
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.
 
859
    :param search: A user text search string.
858
860
    :param log_rev_iterator: An input iterator containing all revisions that
859
861
        could be displayed, in lists.
860
862
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
861
863
        delta).
862
864
    """
863
 
    if match is None:
 
865
    if search is None:
864
866
        return 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):
 
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):
871
872
    for revs in log_rev_iterator:
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])
 
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
 
892
879
 
893
880
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
894
881
    fileids=None, direction='reverse'):
967
954
 
968
955
def _update_fileids(delta, fileids, stop_on):
969
956
    """Update the set of file-ids to search based on file lifecycle events.
970
 
 
 
957
    
971
958
    :param fileids: a set of fileids to update
972
959
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
973
960
      fileids set once their add or remove entry is detected respectively
1014
1001
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
1015
1002
        delta).
1016
1003
    """
 
1004
    repository = branch.repository
1017
1005
    num = 9
1018
1006
    for batch in log_rev_iterator:
1019
1007
        batch = iter(batch)
1068
1056
    if branch_revno != 0:
1069
1057
        if (start_rev_id == _mod_revision.NULL_REVISION
1070
1058
            or end_rev_id == _mod_revision.NULL_REVISION):
1071
 
            raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
 
1059
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
1072
1060
        if start_revno > end_revno:
1073
 
            raise errors.BzrCommandError(gettext("Start revision must be "
1074
 
                                         "older than the end revision."))
 
1061
            raise errors.BzrCommandError("Start revision must be older than "
 
1062
                                         "the end revision.")
1075
1063
    return (start_rev_id, end_rev_id)
1076
1064
 
1077
1065
 
1126
1114
 
1127
1115
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1128
1116
        or (end_rev_id == _mod_revision.NULL_REVISION)):
1129
 
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
 
1117
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
1130
1118
    if start_revno > end_revno:
1131
 
        raise errors.BzrCommandError(gettext("Start revision must be older "
1132
 
                                     "than the end revision."))
 
1119
        raise errors.BzrCommandError("Start revision must be older than "
 
1120
                                     "the end revision.")
1133
1121
 
1134
1122
    if end_revno < start_revno:
1135
1123
        return None, None, None, None
1158
1146
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1159
1147
 
1160
1148
 
 
1149
@deprecated_function(deprecated_in((2, 2, 0)))
 
1150
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
 
1151
    """Filter view_revisions based on revision ranges.
 
1152
 
 
1153
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
 
1154
            tuples to be filtered.
 
1155
 
 
1156
    :param start_rev_id: If not NONE specifies the first revision to be logged.
 
1157
            If NONE then all revisions up to the end_rev_id are logged.
 
1158
 
 
1159
    :param end_rev_id: If not NONE specifies the last revision to be logged.
 
1160
            If NONE then all revisions up to the end of the log are logged.
 
1161
 
 
1162
    :return: The filtered view_revisions.
 
1163
    """
 
1164
    if start_rev_id or end_rev_id:
 
1165
        revision_ids = [r for r, n, d in view_revisions]
 
1166
        if start_rev_id:
 
1167
            start_index = revision_ids.index(start_rev_id)
 
1168
        else:
 
1169
            start_index = 0
 
1170
        if start_rev_id == end_rev_id:
 
1171
            end_index = start_index
 
1172
        else:
 
1173
            if end_rev_id:
 
1174
                end_index = revision_ids.index(end_rev_id)
 
1175
            else:
 
1176
                end_index = len(view_revisions) - 1
 
1177
        # To include the revisions merged into the last revision,
 
1178
        # extend end_rev_id down to, but not including, the next rev
 
1179
        # with the same or lesser merge_depth
 
1180
        end_merge_depth = view_revisions[end_index][2]
 
1181
        try:
 
1182
            for index in xrange(end_index+1, len(view_revisions)+1):
 
1183
                if view_revisions[index][2] <= end_merge_depth:
 
1184
                    end_index = index - 1
 
1185
                    break
 
1186
        except IndexError:
 
1187
            # if the search falls off the end then log to the end as well
 
1188
            end_index = len(view_revisions) - 1
 
1189
        view_revisions = view_revisions[start_index:end_index+1]
 
1190
    return view_revisions
 
1191
 
 
1192
 
1161
1193
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1162
1194
    include_merges=True):
1163
1195
    r"""Return the list of revision ids which touch a given file id.
1242
1274
    return result
1243
1275
 
1244
1276
 
 
1277
@deprecated_function(deprecated_in((2, 2, 0)))
 
1278
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
 
1279
                       include_merges=True):
 
1280
    """Produce an iterator of revisions to show
 
1281
    :return: an iterator of (revision_id, revno, merge_depth)
 
1282
    (if there is no revno for a revision, None is supplied)
 
1283
    """
 
1284
    if not include_merges:
 
1285
        revision_ids = mainline_revs[1:]
 
1286
        if direction == 'reverse':
 
1287
            revision_ids.reverse()
 
1288
        for revision_id in revision_ids:
 
1289
            yield revision_id, str(rev_nos[revision_id]), 0
 
1290
        return
 
1291
    graph = branch.repository.get_graph()
 
1292
    # This asks for all mainline revisions, which means we only have to spider
 
1293
    # sideways, rather than depth history. That said, its still size-of-history
 
1294
    # and should be addressed.
 
1295
    # mainline_revisions always includes an extra revision at the beginning, so
 
1296
    # don't request it.
 
1297
    parent_map = dict(((key, value) for key, value in
 
1298
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
 
1299
    # filter out ghosts; merge_sort errors on ghosts.
 
1300
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
 
1301
    merge_sorted_revisions = tsort.merge_sort(
 
1302
        rev_graph,
 
1303
        mainline_revs[-1],
 
1304
        mainline_revs,
 
1305
        generate_revno=True)
 
1306
 
 
1307
    if direction == 'forward':
 
1308
        # forward means oldest first.
 
1309
        merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
 
1310
    elif direction != 'reverse':
 
1311
        raise ValueError('invalid direction %r' % direction)
 
1312
 
 
1313
    for (sequence, rev_id, merge_depth, revno, end_of_merge
 
1314
         ) in merge_sorted_revisions:
 
1315
        yield rev_id, '.'.join(map(str, revno)), merge_depth
 
1316
 
 
1317
 
1245
1318
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1246
1319
    """Reverse revisions by depth.
1247
1320
 
1344
1417
        """Create a LogFormatter.
1345
1418
 
1346
1419
        :param to_file: the file to output to
1347
 
        :param to_exact_file: if set, gives an output stream to which
 
1420
        :param to_exact_file: if set, gives an output stream to which 
1348
1421
             non-Unicode diffs are written.
1349
1422
        :param show_ids: if True, revision-ids are to be displayed
1350
1423
        :param show_timezone: the timezone to use
1401
1474
            if advice_sep:
1402
1475
                self.to_file.write(advice_sep)
1403
1476
            self.to_file.write(
1404
 
                "Use --include-merged or -n0 to see merged revisions.\n")
 
1477
                "Use --include-merges or -n0 to see merged revisions.\n")
1405
1478
 
1406
1479
    def get_advice_separator(self):
1407
1480
        """Get the text separating the log from the closing advice."""
1586
1659
        if revision.delta is not None:
1587
1660
            # Use the standard status output to display changes
1588
1661
            from bzrlib.delta import report_delta
1589
 
            report_delta(to_file, revision.delta, short_status=False,
 
1662
            report_delta(to_file, revision.delta, short_status=False, 
1590
1663
                         show_ids=self.show_ids, indent=indent)
1591
1664
        if revision.diff is not None:
1592
1665
            to_file.write(indent + 'diff:\n')
1658
1731
        if revision.delta is not None:
1659
1732
            # Use the standard status output to display changes
1660
1733
            from bzrlib.delta import report_delta
1661
 
            report_delta(to_file, revision.delta,
1662
 
                         short_status=self.delta_format==1,
 
1734
            report_delta(to_file, revision.delta, 
 
1735
                         short_status=self.delta_format==1, 
1663
1736
                         show_ids=self.show_ids, indent=indent + offset)
1664
1737
        if revision.diff is not None:
1665
1738
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1785
1858
        return self.get(name)(*args, **kwargs)
1786
1859
 
1787
1860
    def get_default(self, branch):
1788
 
        c = branch.get_config_stack()
1789
 
        return self.get(c.get('log_format'))
 
1861
        return self.get(branch.get_config().log_format())
1790
1862
 
1791
1863
 
1792
1864
log_formatter_registry = LogFormatterRegistry()
1793
1865
 
1794
1866
 
1795
1867
log_formatter_registry.register('short', ShortLogFormatter,
1796
 
                                'Moderately short log format.')
 
1868
                                'Moderately short log format')
1797
1869
log_formatter_registry.register('long', LongLogFormatter,
1798
 
                                'Detailed log format.')
 
1870
                                'Detailed log format')
1799
1871
log_formatter_registry.register('line', LineLogFormatter,
1800
 
                                'Log format with one line per revision.')
 
1872
                                'Log format with one line per revision')
1801
1873
log_formatter_registry.register('gnu-changelog', GnuChangelogLogFormatter,
1802
 
                                'Format used by GNU ChangeLog files.')
 
1874
                                'Format used by GNU ChangeLog files')
1803
1875
 
1804
1876
 
1805
1877
def register_formatter(name, formatter):
1815
1887
    try:
1816
1888
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1817
1889
    except KeyError:
1818
 
        raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
 
1890
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1819
1891
 
1820
1892
 
1821
1893
def author_list_all(rev):
1846
1918
                              'The committer')
1847
1919
 
1848
1920
 
 
1921
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
 
1922
    # deprecated; for compatibility
 
1923
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
 
1924
    lf.show(revno, rev, delta)
 
1925
 
 
1926
 
1849
1927
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1850
1928
                           log_format='long'):
1851
1929
    """Show the change in revision history comparing the old revision history to the new one.
2014
2092
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2015
2093
      branch will be read-locked.
2016
2094
    """
2017
 
    from bzrlib.builtins import _get_revision_range
2018
 
    tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2019
 
        file_list[0])
 
2095
    from builtins import _get_revision_range
 
2096
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2020
2097
    add_cleanup(b.lock_read().unlock)
2021
2098
    # XXX: It's damn messy converting a list of paths to relative paths when
2022
2099
    # those paths might be deleted ones, they might be on a case-insensitive
2111
2188
                          len(row) > 1 and row[1] == 'fixed']
2112
2189
 
2113
2190
        if fixed_bug_urls:
2114
 
            return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
2115
 
                    ' '.join(fixed_bug_urls)}
 
2191
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
2116
2192
    return {}
2117
2193
 
2118
2194
properties_handler_registry.register('bugs_properties_handler',