~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

(jameinel) Bug #581311,
 treat WSAECONNABORTED as ConnectionReset. (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
70
70
    diff,
71
71
    errors,
72
72
    foreign,
 
73
    osutils,
73
74
    repository as _mod_repository,
74
75
    revision as _mod_revision,
75
76
    revisionspec,
 
77
    trace,
76
78
    tsort,
77
 
    i18n,
78
79
    )
79
80
""")
80
81
 
81
82
from bzrlib import (
82
 
    lazy_regex,
83
83
    registry,
84
84
    )
85
85
from bzrlib.osutils import (
86
86
    format_date,
87
87
    format_date_with_offset_in_original_timezone,
88
 
    get_diff_header_encoding,
89
88
    get_terminal_encoding,
90
89
    terminal_width,
91
90
    )
111
110
    revno = 1
112
111
    for revision_id in branch.revision_history():
113
112
        this_inv = branch.repository.get_inventory(revision_id)
114
 
        if this_inv.has_id(file_id):
 
113
        if file_id in this_inv:
115
114
            this_ie = this_inv[file_id]
116
115
            this_path = this_inv.id2path(file_id)
117
116
        else:
232
231
                          delta_type=None,
233
232
                          diff_type=None, _match_using_deltas=True,
234
233
                          exclude_common_ancestry=False,
235
 
                          signature=False,
236
234
                          ):
237
235
    """Convenience function for making a logging request dictionary.
238
236
 
262
260
      generate; 1 for just the mainline; 0 for all levels.
263
261
 
264
262
    :param generate_tags: If True, include tags for matched revisions.
265
 
`
 
263
 
266
264
    :param delta_type: Either 'full', 'partial' or None.
267
265
      'full' means generate the complete delta - adds/deletes/modifies/etc;
268
266
      'partial' means filter the delta using specific_fileids;
280
278
 
281
279
    :param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
282
280
      range operator or as a graph difference.
283
 
 
284
 
    :param signature: show digital signature information
285
281
    """
286
282
    return {
287
283
        'direction': direction,
295
291
        'delta_type': delta_type,
296
292
        'diff_type': diff_type,
297
293
        'exclude_common_ancestry': exclude_common_ancestry,
298
 
        'signature': signature,
299
294
        # Add 'private' attributes for features that may be deprecated
300
295
        '_match_using_deltas': _match_using_deltas,
301
296
    }
303
298
 
304
299
def _apply_log_request_defaults(rqst):
305
300
    """Apply default values to a request dictionary."""
306
 
    result = _DEFAULT_REQUEST_PARAMS.copy()
 
301
    result = _DEFAULT_REQUEST_PARAMS
307
302
    if rqst:
308
303
        result.update(rqst)
309
304
    return result
310
305
 
311
306
 
312
 
def format_signature_validity(rev_id, repo):
313
 
    """get the signature validity
314
 
    
315
 
    :param rev_id: revision id to validate
316
 
    :param repo: repository of revision
317
 
    :return: human readable string to print to log
318
 
    """
319
 
    from bzrlib import gpg
320
 
 
321
 
    gpg_strategy = gpg.GPGStrategy(None)
322
 
    result = repo.verify_revision(rev_id, gpg_strategy)
323
 
    if result[0] == gpg.SIGNATURE_VALID:
324
 
        return "valid signature from {0}".format(result[1])
325
 
    if result[0] == gpg.SIGNATURE_KEY_MISSING:
326
 
        return "unknown key {0}".format(result[1])
327
 
    if result[0] == gpg.SIGNATURE_NOT_VALID:
328
 
        return "invalid signature!"
329
 
    if result[0] == gpg.SIGNATURE_NOT_SIGNED:
330
 
        return "no signature"
331
 
 
332
 
 
333
307
class LogGenerator(object):
334
308
    """A generator of log revisions."""
335
309
 
387
361
            rqst['delta_type'] = None
388
362
        if not getattr(lf, 'supports_diff', False):
389
363
            rqst['diff_type'] = None
390
 
        if not getattr(lf, 'supports_signatures', False):
391
 
            rqst['signature'] = False
392
364
 
393
365
        # Find and print the interesting revisions
394
366
        generator = self._generator_factory(self.branch, rqst)
428
400
        levels = rqst.get('levels')
429
401
        limit = rqst.get('limit')
430
402
        diff_type = rqst.get('diff_type')
431
 
        show_signature = rqst.get('signature')
432
403
        log_count = 0
433
404
        revision_iterator = self._create_log_revision_iterator()
434
405
        for revs in revision_iterator:
440
411
                    diff = None
441
412
                else:
442
413
                    diff = self._format_diff(rev, rev_id, diff_type)
443
 
                if show_signature:
444
 
                    signature = format_signature_validity(rev_id,
445
 
                                                self.branch.repository)
446
 
                else:
447
 
                    signature = None
448
414
                yield LogRevision(rev, revno, merge_depth, delta,
449
 
                    self.rev_tag_dict.get(rev_id), diff, signature)
 
415
                    self.rev_tag_dict.get(rev_id), diff)
450
416
                if limit:
451
417
                    log_count += 1
452
418
                    if log_count >= limit:
466
432
        else:
467
433
            specific_files = None
468
434
        s = StringIO()
469
 
        path_encoding = get_diff_header_encoding()
 
435
        path_encoding = osutils.get_diff_header_encoding()
470
436
        diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
471
437
            new_label='', path_encoding=path_encoding)
472
438
        return s.getvalue()
574
540
        # It's the tip
575
541
        return [(br_rev_id, br_revno, 0)]
576
542
    else:
577
 
        revno_str = _compute_revno_str(branch, rev_id)
 
543
        revno = branch.revision_id_to_dotted_revno(rev_id)
 
544
        revno_str = '.'.join(str(n) for n in revno)
578
545
        return [(rev_id, revno_str, 0)]
579
546
 
580
547
 
660
627
    return len(parents) > 1
661
628
 
662
629
 
663
 
def _compute_revno_str(branch, rev_id):
664
 
    """Compute the revno string from a rev_id.
665
 
 
666
 
    :return: The revno string, or None if the revision is not in the supplied
667
 
        branch.
668
 
    """
669
 
    try:
670
 
        revno = branch.revision_id_to_dotted_revno(rev_id)
671
 
    except errors.NoSuchRevision:
672
 
        # The revision must be outside of this branch
673
 
        return None
674
 
    else:
675
 
        return '.'.join(str(n) for n in revno)
676
 
 
677
 
 
678
630
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
679
631
    """Is start_rev_id an obvious ancestor of end_rev_id?"""
680
632
    if start_rev_id and end_rev_id:
681
 
        try:
682
 
            start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
683
 
            end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
684
 
        except errors.NoSuchRevision:
685
 
            # one or both is not in the branch; not obvious
686
 
            return False
 
633
        start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
 
634
        end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
687
635
        if len(start_dotted) == 1 and len(end_dotted) == 1:
688
636
            # both on mainline
689
637
            return start_dotted[0] <= end_dotted[0]
713
661
    """
714
662
    br_revno, br_rev_id = branch.last_revision_info()
715
663
    repo = branch.repository
716
 
    graph = repo.get_graph()
717
664
    if start_rev_id is None and end_rev_id is None:
718
665
        cur_revno = br_revno
719
 
        for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
720
 
            (_mod_revision.NULL_REVISION,)):
 
666
        for revision_id in repo.iter_reverse_revision_history(br_rev_id):
721
667
            yield revision_id, str(cur_revno), 0
722
668
            cur_revno -= 1
723
669
    else:
724
670
        if end_rev_id is None:
725
671
            end_rev_id = br_rev_id
726
672
        found_start = start_rev_id is None
727
 
        for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
728
 
                (_mod_revision.NULL_REVISION,)):
729
 
            revno_str = _compute_revno_str(branch, revision_id)
 
673
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
 
674
            revno = branch.revision_id_to_dotted_revno(revision_id)
 
675
            revno_str = '.'.join(str(n) for n in revno)
730
676
            if not found_start and revision_id == start_rev_id:
731
677
                if not exclude_common_ancestry:
732
678
                    yield revision_id, revno_str, 0
864
810
    """
865
811
    if search is None:
866
812
        return log_rev_iterator
867
 
    searchRE = lazy_regex.lazy_compile(search, re.IGNORECASE)
 
813
    searchRE = re.compile(search, re.IGNORECASE)
868
814
    return _filter_message_re(searchRE, log_rev_iterator)
869
815
 
870
816
 
1124
1070
    cur_revno = branch_revno
1125
1071
    rev_nos = {}
1126
1072
    mainline_revs = []
1127
 
    graph = branch.repository.get_graph()
1128
 
    for revision_id in graph.iter_lefthand_ancestry(
1129
 
            branch_last_revision, (_mod_revision.NULL_REVISION,)):
 
1073
    for revision_id in branch.repository.iter_reverse_revision_history(
 
1074
                        branch_last_revision):
1130
1075
        if cur_revno < start_revno:
1131
1076
            # We have gone far enough, but we always add 1 more revision
1132
1077
            rev_nos[revision_id] = cur_revno
1198
1143
    This includes the revisions which directly change the file id,
1199
1144
    and the revisions which merge these changes. So if the
1200
1145
    revision graph is::
1201
 
 
1202
1146
        A-.
1203
1147
        |\ \
1204
1148
        B C E
1231
1175
    """
1232
1176
    # Lookup all possible text keys to determine which ones actually modified
1233
1177
    # the file.
1234
 
    graph = branch.repository.get_file_graph()
1235
 
    get_parent_map = graph.get_parent_map
1236
1178
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1237
1179
    next_keys = None
1238
1180
    # Looking up keys in batches of 1000 can cut the time in half, as well as
1242
1184
    #       indexing layer. We might consider passing in hints as to the known
1243
1185
    #       access pattern (sparse/clustered, high success rate/low success
1244
1186
    #       rate). This particular access is clustered with a low success rate.
 
1187
    get_parent_map = branch.repository.texts.get_parent_map
1245
1188
    modified_text_revisions = set()
1246
1189
    chunk_size = 1000
1247
1190
    for start in xrange(0, len(text_keys), chunk_size):
1355
1298
    """
1356
1299
 
1357
1300
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1358
 
                 tags=None, diff=None, signature=None):
 
1301
                 tags=None, diff=None):
1359
1302
        self.rev = rev
1360
 
        if revno is None:
1361
 
            self.revno = None
1362
 
        else:
1363
 
            self.revno = str(revno)
 
1303
        self.revno = str(revno)
1364
1304
        self.merge_depth = merge_depth
1365
1305
        self.delta = delta
1366
1306
        self.tags = tags
1367
1307
        self.diff = diff
1368
 
        self.signature = signature
1369
1308
 
1370
1309
 
1371
1310
class LogFormatter(object):
1380
1319
    to indicate which LogRevision attributes it supports:
1381
1320
 
1382
1321
    - supports_delta must be True if this log formatter supports delta.
1383
 
      Otherwise the delta attribute may not be populated.  The 'delta_format'
1384
 
      attribute describes whether the 'short_status' format (1) or the long
1385
 
      one (2) should be used.
 
1322
        Otherwise the delta attribute may not be populated.  The 'delta_format'
 
1323
        attribute describes whether the 'short_status' format (1) or the long
 
1324
        one (2) should be used.
1386
1325
 
1387
1326
    - supports_merge_revisions must be True if this log formatter supports
1388
 
      merge revisions.  If not, then only mainline revisions will be passed
1389
 
      to the formatter.
 
1327
        merge revisions.  If not, then only mainline revisions will be passed
 
1328
        to the formatter.
1390
1329
 
1391
1330
    - preferred_levels is the number of levels this formatter defaults to.
1392
 
      The default value is zero meaning display all levels.
1393
 
      This value is only relevant if supports_merge_revisions is True.
 
1331
        The default value is zero meaning display all levels.
 
1332
        This value is only relevant if supports_merge_revisions is True.
1394
1333
 
1395
1334
    - supports_tags must be True if this log formatter supports tags.
1396
 
      Otherwise the tags attribute may not be populated.
 
1335
        Otherwise the tags attribute may not be populated.
1397
1336
 
1398
1337
    - supports_diff must be True if this log formatter supports diffs.
1399
 
      Otherwise the diff attribute may not be populated.
1400
 
 
1401
 
    - supports_signatures must be True if this log formatter supports GPG
1402
 
      signatures.
 
1338
        Otherwise the diff attribute may not be populated.
1403
1339
 
1404
1340
    Plugins can register functions to show custom revision properties using
1405
1341
    the properties_handler_registry. The registered function
1406
 
    must respect the following interface description::
1407
 
 
 
1342
    must respect the following interface description:
1408
1343
        def my_show_properties(properties_dict):
1409
1344
            # code that returns a dict {'name':'value'} of the properties
1410
1345
            # to be shown
1597
1532
    supports_delta = True
1598
1533
    supports_tags = True
1599
1534
    supports_diff = True
1600
 
    supports_signatures = True
1601
1535
 
1602
1536
    def __init__(self, *args, **kwargs):
1603
1537
        super(LongLogFormatter, self).__init__(*args, **kwargs)
1623
1557
                self.merge_marker(revision)))
1624
1558
        if revision.tags:
1625
1559
            lines.append('tags: %s' % (', '.join(revision.tags)))
1626
 
        if self.show_ids or revision.revno is None:
 
1560
        if self.show_ids:
1627
1561
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
1628
 
        if self.show_ids:
1629
1562
            for parent_id in revision.rev.parent_ids:
1630
1563
                lines.append('parent: %s' % (parent_id,))
1631
1564
        lines.extend(self.custom_properties(revision.rev))
1642
1575
 
1643
1576
        lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1644
1577
 
1645
 
        if revision.signature is not None:
1646
 
            lines.append('signature: ' + revision.signature)
1647
 
 
1648
1578
        lines.append('message:')
1649
1579
        if not revision.rev.message:
1650
1580
            lines.append('  (no message)')
1697
1627
        indent = '    ' * depth
1698
1628
        revno_width = self.revno_width_by_depth.get(depth)
1699
1629
        if revno_width is None:
1700
 
            if revision.revno is None or revision.revno.find('.') == -1:
 
1630
            if revision.revno.find('.') == -1:
1701
1631
                # mainline revno, e.g. 12345
1702
1632
                revno_width = 5
1703
1633
            else:
1711
1641
        if revision.tags:
1712
1642
            tags = ' {%s}' % (', '.join(revision.tags))
1713
1643
        to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1714
 
                revision.revno or "", self.short_author(revision.rev),
 
1644
                revision.revno, self.short_author(revision.rev),
1715
1645
                format_date(revision.rev.timestamp,
1716
1646
                            revision.rev.timezone or 0,
1717
1647
                            self.show_timezone, date_fmt="%Y-%m-%d",
1718
1648
                            show_offset=False),
1719
1649
                tags, self.merge_marker(revision)))
1720
1650
        self.show_properties(revision.rev, indent+offset)
1721
 
        if self.show_ids or revision.revno is None:
 
1651
        if self.show_ids:
1722
1652
            to_file.write(indent + offset + 'revision-id:%s\n'
1723
1653
                          % (revision.rev.revision_id,))
1724
1654
        if not revision.rev.message:
1777
1707
 
1778
1708
    def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1779
1709
        """Format log info into one string. Truncate tail of string
1780
 
 
1781
 
        :param revno:      revision number or None.
1782
 
                           Revision numbers counts from 1.
1783
 
        :param rev:        revision object
1784
 
        :param max_chars:  maximum length of resulting string
1785
 
        :param tags:       list of tags or None
1786
 
        :param prefix:     string to prefix each line
1787
 
        :return:           formatted truncated string
 
1710
        :param  revno:      revision number or None.
 
1711
                            Revision numbers counts from 1.
 
1712
        :param  rev:        revision object
 
1713
        :param  max_chars:  maximum length of resulting string
 
1714
        :param  tags:       list of tags or None
 
1715
        :param  prefix:     string to prefix each line
 
1716
        :return:            formatted truncated string
1788
1717
        """
1789
1718
        out = []
1790
1719
        if revno:
1791
1720
            # show revno only when is not None
1792
1721
            out.append("%s:" % revno)
1793
 
        if max_chars is not None:
1794
 
            out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1795
 
        else:
1796
 
            out.append(self.short_author(rev))
 
1722
        out.append(self.truncate(self.short_author(rev), 20))
1797
1723
        out.append(self.date_string(rev))
1798
1724
        if len(rev.parent_ids) > 1:
1799
1725
            out.append('[merge]')
1992
1918
    old_revisions = set()
1993
1919
    new_history = []
1994
1920
    new_revisions = set()
1995
 
    graph = repository.get_graph()
1996
 
    new_iter = graph.iter_lefthand_ancestry(new_revision_id)
1997
 
    old_iter = graph.iter_lefthand_ancestry(old_revision_id)
 
1921
    new_iter = repository.iter_reverse_revision_history(new_revision_id)
 
1922
    old_iter = repository.iter_reverse_revision_history(old_revision_id)
1998
1923
    stop_revision = None
1999
1924
    do_old = True
2000
1925
    do_new = True