~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

(gz) Remove bzrlib/util/elementtree/ package (Martin Packman)

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,
75
75
    revisionspec,
76
76
    tsort,
77
77
    )
 
78
from bzrlib.i18n import gettext, ngettext
78
79
""")
79
80
 
80
81
from bzrlib import (
 
82
    lazy_regex,
81
83
    registry,
82
84
    )
83
85
from bzrlib.osutils import (
87
89
    get_terminal_encoding,
88
90
    terminal_width,
89
91
    )
90
 
from bzrlib.symbol_versioning import (
91
 
    deprecated_function,
92
 
    deprecated_in,
93
 
    )
94
92
 
95
93
 
96
94
def find_touching_revisions(branch, file_id):
107
105
    last_ie = None
108
106
    last_path = None
109
107
    revno = 1
110
 
    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):
111
112
        this_inv = branch.repository.get_inventory(revision_id)
112
 
        if file_id in this_inv:
 
113
        if this_inv.has_id(file_id):
113
114
            this_ie = this_inv[file_id]
114
115
            this_path = this_inv.id2path(file_id)
115
116
        else:
155
156
             end_revision=None,
156
157
             search=None,
157
158
             limit=None,
158
 
             show_diff=False):
 
159
             show_diff=False,
 
160
             match=None):
159
161
    """Write out human-readable log of commits to this branch.
160
162
 
161
163
    This function is being retained for backwards compatibility but
184
186
        if None or 0.
185
187
 
186
188
    :param show_diff: If True, output a diff after each revision.
 
189
 
 
190
    :param match: Dictionary of search lists to use when matching revision
 
191
      properties.
187
192
    """
188
193
    # Convert old-style parameters to new-style parameters
189
194
    if specific_fileid is not None:
213
218
    Logger(branch, rqst).show(lf)
214
219
 
215
220
 
216
 
# Note: This needs to be kept this in sync with the defaults in
 
221
# Note: This needs to be kept in sync with the defaults in
217
222
# make_log_request_dict() below
218
223
_DEFAULT_REQUEST_PARAMS = {
219
224
    'direction': 'reverse',
220
 
    'levels': 1,
 
225
    'levels': None,
221
226
    'generate_tags': True,
222
227
    'exclude_common_ancestry': False,
223
228
    '_match_using_deltas': True,
226
231
 
227
232
def make_log_request_dict(direction='reverse', specific_fileids=None,
228
233
                          start_revision=None, end_revision=None, limit=None,
229
 
                          message_search=None, levels=1, generate_tags=True,
 
234
                          message_search=None, levels=None, generate_tags=True,
230
235
                          delta_type=None,
231
236
                          diff_type=None, _match_using_deltas=True,
232
 
                          exclude_common_ancestry=False,
 
237
                          exclude_common_ancestry=False, match=None,
 
238
                          signature=False, omit_merges=False,
233
239
                          ):
234
240
    """Convenience function for making a logging request dictionary.
235
241
 
256
262
      matching commit messages
257
263
 
258
264
    :param levels: the number of levels of revisions to
259
 
      generate; 1 for just the mainline; 0 for all levels.
 
265
      generate; 1 for just the mainline; 0 for all levels, or None for
 
266
      a sensible default.
260
267
 
261
268
    :param generate_tags: If True, include tags for matched revisions.
262
 
 
 
269
`
263
270
    :param delta_type: Either 'full', 'partial' or None.
264
271
      'full' means generate the complete delta - adds/deletes/modifies/etc;
265
272
      'partial' means filter the delta using specific_fileids;
277
284
 
278
285
    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
279
286
      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
 
280
297
    """
 
298
    # Take care of old style message_search parameter
 
299
    if message_search:
 
300
        if match:
 
301
            if 'message' in match:
 
302
                match['message'].append(message_search)
 
303
            else:
 
304
                match['message'] = [message_search]
 
305
        else:
 
306
            match={ 'message': [message_search] }
281
307
    return {
282
308
        'direction': direction,
283
309
        'specific_fileids': specific_fileids,
284
310
        'start_revision': start_revision,
285
311
        'end_revision': end_revision,
286
312
        'limit': limit,
287
 
        'message_search': message_search,
288
313
        'levels': levels,
289
314
        'generate_tags': generate_tags,
290
315
        'delta_type': delta_type,
291
316
        'diff_type': diff_type,
292
317
        'exclude_common_ancestry': exclude_common_ancestry,
 
318
        'signature': signature,
 
319
        'match': match,
 
320
        'omit_merges': omit_merges,
293
321
        # Add 'private' attributes for features that may be deprecated
294
322
        '_match_using_deltas': _match_using_deltas,
295
323
    }
303
331
    return result
304
332
 
305
333
 
 
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_signature(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
 
306
355
class LogGenerator(object):
307
356
    """A generator of log revisions."""
308
357
 
353
402
        # Tweak the LogRequest based on what the LogFormatter can handle.
354
403
        # (There's no point generating stuff if the formatter can't display it.)
355
404
        rqst = self.rqst
356
 
        rqst['levels'] = lf.get_levels()
 
405
        if rqst['levels'] is None or lf.get_levels() > rqst['levels']:
 
406
            # user didn't specify levels, use whatever the LF can handle:
 
407
            rqst['levels'] = lf.get_levels()
 
408
 
357
409
        if not getattr(lf, 'supports_tags', False):
358
410
            rqst['generate_tags'] = False
359
411
        if not getattr(lf, 'supports_delta', False):
360
412
            rqst['delta_type'] = None
361
413
        if not getattr(lf, 'supports_diff', False):
362
414
            rqst['diff_type'] = None
 
415
        if not getattr(lf, 'supports_signatures', False):
 
416
            rqst['signature'] = False
363
417
 
364
418
        # Find and print the interesting revisions
365
419
        generator = self._generator_factory(self.branch, rqst)
369
423
 
370
424
    def _generator_factory(self, branch, rqst):
371
425
        """Make the LogGenerator object to use.
372
 
        
 
426
 
373
427
        Subclasses may wish to override this.
374
428
        """
375
429
        return _DefaultLogGenerator(branch, rqst)
399
453
        levels = rqst.get('levels')
400
454
        limit = rqst.get('limit')
401
455
        diff_type = rqst.get('diff_type')
 
456
        show_signature = rqst.get('signature')
 
457
        omit_merges = rqst.get('omit_merges')
402
458
        log_count = 0
403
459
        revision_iterator = self._create_log_revision_iterator()
404
460
        for revs in revision_iterator:
406
462
                # 0 levels means show everything; merge_depth counts from 0
407
463
                if levels != 0 and merge_depth >= levels:
408
464
                    continue
 
465
                if omit_merges and len(rev.parent_ids) > 1:
 
466
                    continue
409
467
                if diff_type is None:
410
468
                    diff = None
411
469
                else:
412
470
                    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
413
476
                yield LogRevision(rev, revno, merge_depth, delta,
414
 
                    self.rev_tag_dict.get(rev_id), diff)
 
477
                    self.rev_tag_dict.get(rev_id), diff, signature)
415
478
                if limit:
416
479
                    log_count += 1
417
480
                    if log_count >= limit:
472
535
 
473
536
        # Apply the other filters
474
537
        return make_log_rev_iterator(self.branch, view_revisions,
475
 
            rqst.get('delta_type'), rqst.get('message_search'),
 
538
            rqst.get('delta_type'), rqst.get('match'),
476
539
            file_ids=rqst.get('specific_fileids'),
477
540
            direction=rqst.get('direction'))
478
541
 
491
554
            rqst.get('specific_fileids')[0], view_revisions,
492
555
            include_merges=rqst.get('levels') != 1)
493
556
        return make_log_rev_iterator(self.branch, view_revisions,
494
 
            rqst.get('delta_type'), rqst.get('message_search'))
 
557
            rqst.get('delta_type'), rqst.get('match'))
495
558
 
496
559
 
497
560
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
505
568
             a list of the same tuples.
506
569
    """
507
570
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
508
 
        raise errors.BzrCommandError(
509
 
            '--exclude-common-ancestry requires two different revisions')
 
571
        raise errors.BzrCommandError(gettext(
 
572
            '--exclude-common-ancestry requires two different revisions'))
510
573
    if direction not in ('reverse', 'forward'):
511
 
        raise ValueError('invalid direction %r' % direction)
 
574
        raise ValueError(gettext('invalid direction %r') % direction)
512
575
    br_revno, br_rev_id = branch.last_revision_info()
513
576
    if br_revno == 0:
514
577
        return []
517
580
        and (not generate_merge_revisions
518
581
             or not _has_merges(branch, end_rev_id))):
519
582
        # If a single revision is requested, check we can handle it
520
 
        iter_revs = _generate_one_revision(branch, end_rev_id, br_rev_id,
521
 
                                           br_revno)
522
 
    elif not generate_merge_revisions:
523
 
        # If we only want to see linear revisions, we can iterate ...
524
 
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
525
 
                                             direction, exclude_common_ancestry)
526
 
        if direction == 'forward':
527
 
            iter_revs = reversed(iter_revs)
528
 
    else:
529
 
        iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
530
 
                                            direction, delayed_graph_generation,
531
 
                                            exclude_common_ancestry)
532
 
        if direction == 'forward':
533
 
            iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
 
583
        return  _generate_one_revision(branch, end_rev_id, br_rev_id,
 
584
                                       br_revno)
 
585
    if not generate_merge_revisions:
 
586
        try:
 
587
            # If we only want to see linear revisions, we can iterate ...
 
588
            iter_revs = _linear_view_revisions(
 
589
                branch, start_rev_id, end_rev_id,
 
590
                exclude_common_ancestry=exclude_common_ancestry)
 
591
            # If a start limit was given and it's not obviously an
 
592
            # ancestor of the end limit, check it before outputting anything
 
593
            if (direction == 'forward'
 
594
                or (start_rev_id and not _is_obvious_ancestor(
 
595
                        branch, start_rev_id, end_rev_id))):
 
596
                    iter_revs = list(iter_revs)
 
597
            if direction == 'forward':
 
598
                iter_revs = reversed(iter_revs)
 
599
            return iter_revs
 
600
        except _StartNotLinearAncestor:
 
601
            # Switch to the slower implementation that may be able to find a
 
602
            # non-obvious ancestor out of the left-hand history.
 
603
            pass
 
604
    iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
 
605
                                        direction, delayed_graph_generation,
 
606
                                        exclude_common_ancestry)
 
607
    if direction == 'forward':
 
608
        iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
534
609
    return iter_revs
535
610
 
536
611
 
543
618
        return [(rev_id, revno_str, 0)]
544
619
 
545
620
 
546
 
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
547
 
                             exclude_common_ancestry=False):
548
 
    result = _linear_view_revisions(
549
 
        branch, start_rev_id, end_rev_id,
550
 
        exclude_common_ancestry=exclude_common_ancestry)
551
 
    # If a start limit was given and it's not obviously an
552
 
    # ancestor of the end limit, check it before outputting anything
553
 
    if direction == 'forward' or (start_rev_id
554
 
        and not _is_obvious_ancestor(branch, start_rev_id, end_rev_id)):
555
 
        try:
556
 
            result = list(result)
557
 
        except _StartNotLinearAncestor:
558
 
            raise errors.BzrCommandError('Start revision not found in'
559
 
                ' left-hand history of end revision.')
560
 
    return result
561
 
 
562
 
 
563
621
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
564
622
                            delayed_graph_generation,
565
623
                            exclude_common_ancestry=False):
601
659
        except _StartNotLinearAncestor:
602
660
            # A merge was never detected so the lower revision limit can't
603
661
            # be nested down somewhere
604
 
            raise errors.BzrCommandError('Start revision not found in'
605
 
                ' history of end revision.')
 
662
            raise errors.BzrCommandError(gettext('Start revision not found in'
 
663
                ' history of end revision.'))
606
664
 
607
665
    # We exit the loop above because we encounter a revision with merges, from
608
666
    # this revision, we need to switch to _graph_view_revisions.
678
736
    """
679
737
    br_revno, br_rev_id = branch.last_revision_info()
680
738
    repo = branch.repository
 
739
    graph = repo.get_graph()
681
740
    if start_rev_id is None and end_rev_id is None:
682
741
        cur_revno = br_revno
683
 
        for revision_id in repo.iter_reverse_revision_history(br_rev_id):
 
742
        for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
 
743
            (_mod_revision.NULL_REVISION,)):
684
744
            yield revision_id, str(cur_revno), 0
685
745
            cur_revno -= 1
686
746
    else:
687
747
        if end_rev_id is None:
688
748
            end_rev_id = br_rev_id
689
749
        found_start = start_rev_id is None
690
 
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
 
750
        for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
 
751
                (_mod_revision.NULL_REVISION,)):
691
752
            revno_str = _compute_revno_str(branch, revision_id)
692
753
            if not found_start and revision_id == start_rev_id:
693
754
                if not exclude_common_ancestry:
745
806
            yield rev_id, '.'.join(map(str, revno)), merge_depth
746
807
 
747
808
 
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
 
 
767
809
def _rebase_merge_depth(view_revisions):
768
810
    """Adjust depths upwards so the top level is 0."""
769
811
    # If either the first or last revision have a merge_depth of 0, we're done
813
855
    return log_rev_iterator
814
856
 
815
857
 
816
 
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
 
858
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
817
859
    """Create a filtered iterator of log_rev_iterator matching on a regex.
818
860
 
819
861
    :param branch: The branch being logged.
820
862
    :param generate_delta: Whether to generate a delta for each revision.
821
 
    :param search: A user text search string.
 
863
    :param match: A dictionary with properties as keys and lists of strings
 
864
        as values. To match, a revision may match any of the supplied strings
 
865
        within a single property but must match at least one string for each
 
866
        property.
822
867
    :param log_rev_iterator: An input iterator containing all revisions that
823
868
        could be displayed, in lists.
824
869
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
825
870
        delta).
826
871
    """
827
 
    if search is None:
 
872
    if match is None:
828
873
        return 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):
 
874
    searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
 
875
                for (k,v) in match.iteritems()]
 
876
    return _filter_re(searchRE, log_rev_iterator)
 
877
 
 
878
 
 
879
def _filter_re(searchRE, log_rev_iterator):
834
880
    for revs in log_rev_iterator:
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
 
 
 
881
        new_revs = [rev for rev in revs if _match_filter(searchRE, rev[1])]
 
882
        if new_revs:
 
883
            yield new_revs
 
884
 
 
885
def _match_filter(searchRE, rev):
 
886
    strings = {
 
887
               'message': (rev.message,),
 
888
               'committer': (rev.committer,),
 
889
               'author': (rev.get_apparent_authors()),
 
890
               'bugs': list(rev.iter_bugs())
 
891
               }
 
892
    strings[''] = [item for inner_list in strings.itervalues()
 
893
                   for item in inner_list]
 
894
    for (k,v) in searchRE:
 
895
        if k in strings and not _match_any_filter(strings[k], v):
 
896
            return False
 
897
    return True
 
898
 
 
899
def _match_any_filter(strings, res):
 
900
    return any([filter(None, map(re.search, strings)) for re in res])
841
901
 
842
902
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
843
903
    fileids=None, direction='reverse'):
916
976
 
917
977
def _update_fileids(delta, fileids, stop_on):
918
978
    """Update the set of file-ids to search based on file lifecycle events.
919
 
    
 
979
 
920
980
    :param fileids: a set of fileids to update
921
981
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
922
982
      fileids set once their add or remove entry is detected respectively
963
1023
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
964
1024
        delta).
965
1025
    """
966
 
    repository = branch.repository
967
1026
    num = 9
968
1027
    for batch in log_rev_iterator:
969
1028
        batch = iter(batch)
1018
1077
    if branch_revno != 0:
1019
1078
        if (start_rev_id == _mod_revision.NULL_REVISION
1020
1079
            or end_rev_id == _mod_revision.NULL_REVISION):
1021
 
            raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
1080
            raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1022
1081
        if start_revno > end_revno:
1023
 
            raise errors.BzrCommandError("Start revision must be older than "
1024
 
                                         "the end revision.")
 
1082
            raise errors.BzrCommandError(gettext("Start revision must be "
 
1083
                                         "older than the end revision."))
1025
1084
    return (start_rev_id, end_rev_id)
1026
1085
 
1027
1086
 
1076
1135
 
1077
1136
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1078
1137
        or (end_rev_id == _mod_revision.NULL_REVISION)):
1079
 
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
1138
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1080
1139
    if start_revno > end_revno:
1081
 
        raise errors.BzrCommandError("Start revision must be older than "
1082
 
                                     "the end revision.")
 
1140
        raise errors.BzrCommandError(gettext("Start revision must be older "
 
1141
                                     "than the end revision."))
1083
1142
 
1084
1143
    if end_revno < start_revno:
1085
1144
        return None, None, None, None
1086
1145
    cur_revno = branch_revno
1087
1146
    rev_nos = {}
1088
1147
    mainline_revs = []
1089
 
    for revision_id in branch.repository.iter_reverse_revision_history(
1090
 
                        branch_last_revision):
 
1148
    graph = branch.repository.get_graph()
 
1149
    for revision_id in graph.iter_lefthand_ancestry(
 
1150
            branch_last_revision, (_mod_revision.NULL_REVISION,)):
1091
1151
        if cur_revno < start_revno:
1092
1152
            # We have gone far enough, but we always add 1 more revision
1093
1153
            rev_nos[revision_id] = cur_revno
1107
1167
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1108
1168
 
1109
1169
 
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
 
 
1154
1170
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1155
1171
    include_merges=True):
1156
1172
    r"""Return the list of revision ids which touch a given file id.
1235
1251
    return result
1236
1252
 
1237
1253
 
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
 
 
1279
1254
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1280
1255
    """Reverse revisions by depth.
1281
1256
 
1316
1291
    """
1317
1292
 
1318
1293
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1319
 
                 tags=None, diff=None):
 
1294
                 tags=None, diff=None, signature=None):
1320
1295
        self.rev = rev
1321
1296
        if revno is None:
1322
1297
            self.revno = None
1326
1301
        self.delta = delta
1327
1302
        self.tags = tags
1328
1303
        self.diff = diff
 
1304
        self.signature = signature
1329
1305
 
1330
1306
 
1331
1307
class LogFormatter(object):
1358
1334
    - supports_diff must be True if this log formatter supports diffs.
1359
1335
      Otherwise the diff attribute may not be populated.
1360
1336
 
 
1337
    - supports_signatures must be True if this log formatter supports GPG
 
1338
      signatures.
 
1339
 
1361
1340
    Plugins can register functions to show custom revision properties using
1362
1341
    the properties_handler_registry. The registered function
1363
1342
    must respect the following interface description::
1374
1353
        """Create a LogFormatter.
1375
1354
 
1376
1355
        :param to_file: the file to output to
1377
 
        :param to_exact_file: if set, gives an output stream to which 
 
1356
        :param to_exact_file: if set, gives an output stream to which
1378
1357
             non-Unicode diffs are written.
1379
1358
        :param show_ids: if True, revision-ids are to be displayed
1380
1359
        :param show_timezone: the timezone to use
1431
1410
            if advice_sep:
1432
1411
                self.to_file.write(advice_sep)
1433
1412
            self.to_file.write(
1434
 
                "Use --include-merges or -n0 to see merged revisions.\n")
 
1413
                "Use --include-merged or -n0 to see merged revisions.\n")
1435
1414
 
1436
1415
    def get_advice_separator(self):
1437
1416
        """Get the text separating the log from the closing advice."""
1554
1533
    supports_delta = True
1555
1534
    supports_tags = True
1556
1535
    supports_diff = True
 
1536
    supports_signatures = True
1557
1537
 
1558
1538
    def __init__(self, *args, **kwargs):
1559
1539
        super(LongLogFormatter, self).__init__(*args, **kwargs)
1598
1578
 
1599
1579
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1600
1580
 
 
1581
        if revision.signature is not None:
 
1582
            lines.append('signature: ' + revision.signature)
 
1583
 
1601
1584
        lines.append('message:')
1602
1585
        if not revision.rev.message:
1603
1586
            lines.append('  (no message)')
1612
1595
        if revision.delta is not None:
1613
1596
            # Use the standard status output to display changes
1614
1597
            from bzrlib.delta import report_delta
1615
 
            report_delta(to_file, revision.delta, short_status=False, 
 
1598
            report_delta(to_file, revision.delta, short_status=False,
1616
1599
                         show_ids=self.show_ids, indent=indent)
1617
1600
        if revision.diff is not None:
1618
1601
            to_file.write(indent + 'diff:\n')
1684
1667
        if revision.delta is not None:
1685
1668
            # Use the standard status output to display changes
1686
1669
            from bzrlib.delta import report_delta
1687
 
            report_delta(to_file, revision.delta, 
1688
 
                         short_status=self.delta_format==1, 
 
1670
            report_delta(to_file, revision.delta,
 
1671
                         short_status=self.delta_format==1,
1689
1672
                         show_ids=self.show_ids, indent=indent + offset)
1690
1673
        if revision.diff is not None:
1691
1674
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1811
1794
        return self.get(name)(*args, **kwargs)
1812
1795
 
1813
1796
    def get_default(self, branch):
1814
 
        return self.get(branch.get_config().log_format())
 
1797
        c = branch.get_config_stack()
 
1798
        return self.get(c.get('log_format'))
1815
1799
 
1816
1800
 
1817
1801
log_formatter_registry = LogFormatterRegistry()
1818
1802
 
1819
1803
 
1820
1804
log_formatter_registry.register('short', ShortLogFormatter,
1821
 
                                'Moderately short log format')
 
1805
                                'Moderately short log format.')
1822
1806
log_formatter_registry.register('long', LongLogFormatter,
1823
 
                                'Detailed log format')
 
1807
                                'Detailed log format.')
1824
1808
log_formatter_registry.register('line', LineLogFormatter,
1825
 
                                'Log format with one line per revision')
 
1809
                                'Log format with one line per revision.')
1826
1810
log_formatter_registry.register('gnu-changelog', GnuChangelogLogFormatter,
1827
 
                                'Format used by GNU ChangeLog files')
 
1811
                                'Format used by GNU ChangeLog files.')
1828
1812
 
1829
1813
 
1830
1814
def register_formatter(name, formatter):
1840
1824
    try:
1841
1825
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1842
1826
    except KeyError:
1843
 
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
 
1827
        raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
1844
1828
 
1845
1829
 
1846
1830
def author_list_all(rev):
1871
1855
                              'The committer')
1872
1856
 
1873
1857
 
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
 
 
1880
1858
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1881
1859
                           log_format='long'):
1882
1860
    """Show the change in revision history comparing the old revision history to the new one.
1945
1923
    old_revisions = set()
1946
1924
    new_history = []
1947
1925
    new_revisions = set()
1948
 
    new_iter = repository.iter_reverse_revision_history(new_revision_id)
1949
 
    old_iter = repository.iter_reverse_revision_history(old_revision_id)
 
1926
    graph = repository.get_graph()
 
1927
    new_iter = graph.iter_lefthand_ancestry(new_revision_id)
 
1928
    old_iter = graph.iter_lefthand_ancestry(old_revision_id)
1950
1929
    stop_revision = None
1951
1930
    do_old = True
1952
1931
    do_new = True
2044
2023
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2045
2024
      branch will be read-locked.
2046
2025
    """
2047
 
    from builtins import _get_revision_range
2048
 
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
 
2026
    from bzrlib.builtins import _get_revision_range
 
2027
    tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
 
2028
        file_list[0])
2049
2029
    add_cleanup(b.lock_read().unlock)
2050
2030
    # XXX: It's damn messy converting a list of paths to relative paths when
2051
2031
    # those paths might be deleted ones, they might be on a case-insensitive
2140
2120
                          len(row) > 1 and row[1] == 'fixed']
2141
2121
 
2142
2122
        if fixed_bug_urls:
2143
 
            return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
 
2123
            return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
 
2124
                    ' '.join(fixed_bug_urls)}
2144
2125
    return {}
2145
2126
 
2146
2127
properties_handler_registry.register('bugs_properties_handler',