/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-20 14:27:19 UTC
  • mto: This revision was merged to the branch mainline in revision 5837.
  • Revision ID: john@arbash-meinel.com-20110420142719-advs1k5vztqzbrgv
Fix bug #767177. Be more agressive with file.close() calls.

Our test suite gets a number of thread leaks and failures because it happens to get async
SFTPFile.close() calls. (if an SFTPFile closes due to __del__ it is done as an async request,
while if you call SFTPFile.close() it is done as a synchronous request.)
We have a couple other cases, probably. Namely SFTPTransport.get() also does an async
prefetch of the content, so if you don't .read() you'll also leak threads that think they
are doing work that you want.

The biggest change here, though, is using a try/finally in a generator, which is not 
python2.4 compatible.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
73
73
    repository as _mod_repository,
74
74
    revision as _mod_revision,
75
75
    revisionspec,
76
 
    trace,
77
76
    tsort,
78
77
    )
79
78
""")
84
83
from bzrlib.osutils import (
85
84
    format_date,
86
85
    format_date_with_offset_in_original_timezone,
 
86
    get_diff_header_encoding,
87
87
    get_terminal_encoding,
88
 
    re_compile_checked,
89
88
    terminal_width,
90
89
    )
91
90
from bzrlib.symbol_versioning import (
298
297
 
299
298
def _apply_log_request_defaults(rqst):
300
299
    """Apply default values to a request dictionary."""
301
 
    result = _DEFAULT_REQUEST_PARAMS
 
300
    result = _DEFAULT_REQUEST_PARAMS.copy()
302
301
    if rqst:
303
302
        result.update(rqst)
304
303
    return result
432
431
        else:
433
432
            specific_files = None
434
433
        s = StringIO()
 
434
        path_encoding = get_diff_header_encoding()
435
435
        diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
436
 
            new_label='')
 
436
            new_label='', path_encoding=path_encoding)
437
437
        return s.getvalue()
438
438
 
439
439
    def _create_log_revision_iterator(self):
522
522
    elif not generate_merge_revisions:
523
523
        # If we only want to see linear revisions, we can iterate ...
524
524
        iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
525
 
                                             direction)
 
525
                                             direction, exclude_common_ancestry)
526
526
        if direction == 'forward':
527
527
            iter_revs = reversed(iter_revs)
528
528
    else:
539
539
        # It's the tip
540
540
        return [(br_rev_id, br_revno, 0)]
541
541
    else:
542
 
        revno = branch.revision_id_to_dotted_revno(rev_id)
543
 
        revno_str = '.'.join(str(n) for n in revno)
 
542
        revno_str = _compute_revno_str(branch, rev_id)
544
543
        return [(rev_id, revno_str, 0)]
545
544
 
546
545
 
547
 
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction):
548
 
    result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
 
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)
549
551
    # If a start limit was given and it's not obviously an
550
552
    # ancestor of the end limit, check it before outputting anything
551
553
    if direction == 'forward' or (start_rev_id
572
574
    if delayed_graph_generation:
573
575
        try:
574
576
            for rev_id, revno, depth in  _linear_view_revisions(
575
 
                branch, start_rev_id, end_rev_id):
 
577
                branch, start_rev_id, end_rev_id, exclude_common_ancestry):
576
578
                if _has_merges(branch, rev_id):
577
579
                    # The end_rev_id can be nested down somewhere. We need an
578
580
                    # explicit ancestry check. There is an ambiguity here as we
623
625
    return len(parents) > 1
624
626
 
625
627
 
 
628
def _compute_revno_str(branch, rev_id):
 
629
    """Compute the revno string from a rev_id.
 
630
 
 
631
    :return: The revno string, or None if the revision is not in the supplied
 
632
        branch.
 
633
    """
 
634
    try:
 
635
        revno = branch.revision_id_to_dotted_revno(rev_id)
 
636
    except errors.NoSuchRevision:
 
637
        # The revision must be outside of this branch
 
638
        return None
 
639
    else:
 
640
        return '.'.join(str(n) for n in revno)
 
641
 
 
642
 
626
643
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
627
644
    """Is start_rev_id an obvious ancestor of end_rev_id?"""
628
645
    if start_rev_id and end_rev_id:
629
 
        start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
630
 
        end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
 
646
        try:
 
647
            start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
 
648
            end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
 
649
        except errors.NoSuchRevision:
 
650
            # one or both is not in the branch; not obvious
 
651
            return False
631
652
        if len(start_dotted) == 1 and len(end_dotted) == 1:
632
653
            # both on mainline
633
654
            return start_dotted[0] <= end_dotted[0]
643
664
    return True
644
665
 
645
666
 
646
 
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
 
667
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
 
668
                           exclude_common_ancestry=False):
647
669
    """Calculate a sequence of revisions to view, newest to oldest.
648
670
 
649
671
    :param start_rev_id: the lower revision-id
650
672
    :param end_rev_id: the upper revision-id
 
673
    :param exclude_common_ancestry: Whether the start_rev_id should be part of
 
674
        the iterated revisions.
651
675
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
652
676
    :raises _StartNotLinearAncestor: if a start_rev_id is specified but
653
 
      is not found walking the left-hand history
 
677
        is not found walking the left-hand history
654
678
    """
655
679
    br_revno, br_rev_id = branch.last_revision_info()
656
680
    repo = branch.repository
664
688
            end_rev_id = br_rev_id
665
689
        found_start = start_rev_id is None
666
690
        for revision_id in repo.iter_reverse_revision_history(end_rev_id):
667
 
            revno = branch.revision_id_to_dotted_revno(revision_id)
668
 
            revno_str = '.'.join(str(n) for n in revno)
 
691
            revno_str = _compute_revno_str(branch, revision_id)
669
692
            if not found_start and revision_id == start_rev_id:
670
 
                yield revision_id, revno_str, 0
 
693
                if not exclude_common_ancestry:
 
694
                    yield revision_id, revno_str, 0
671
695
                found_start = True
672
696
                break
673
697
            else:
802
826
    """
803
827
    if search is None:
804
828
        return log_rev_iterator
805
 
    searchRE = re_compile_checked(search, re.IGNORECASE,
806
 
            'log message filter')
 
829
    searchRE = re.compile(search, re.IGNORECASE)
807
830
    return _filter_message_re(searchRE, log_rev_iterator)
808
831
 
809
832
 
1293
1316
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
1294
1317
                 tags=None, diff=None):
1295
1318
        self.rev = rev
1296
 
        self.revno = str(revno)
 
1319
        if revno is None:
 
1320
            self.revno = None
 
1321
        else:
 
1322
            self.revno = str(revno)
1297
1323
        self.merge_depth = merge_depth
1298
1324
        self.delta = delta
1299
1325
        self.tags = tags
1341
1367
 
1342
1368
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1343
1369
                 delta_format=None, levels=None, show_advice=False,
1344
 
                 to_exact_file=None):
 
1370
                 to_exact_file=None, author_list_handler=None):
1345
1371
        """Create a LogFormatter.
1346
1372
 
1347
1373
        :param to_file: the file to output to
1355
1381
          let the log formatter decide.
1356
1382
        :param show_advice: whether to show advice at the end of the
1357
1383
          log or not
 
1384
        :param author_list_handler: callable generating a list of
 
1385
          authors to display for a given revision
1358
1386
        """
1359
1387
        self.to_file = to_file
1360
1388
        # 'exact' stream used to show diff, it should print content 'as is'
1375
1403
        self.levels = levels
1376
1404
        self._show_advice = show_advice
1377
1405
        self._merge_count = 0
 
1406
        self._author_list_handler = author_list_handler
1378
1407
 
1379
1408
    def get_levels(self):
1380
1409
        """Get the number of levels to display or 0 for all."""
1412
1441
        return address
1413
1442
 
1414
1443
    def short_author(self, rev):
1415
 
        name, address = config.parse_username(rev.get_apparent_authors()[0])
1416
 
        if name:
1417
 
            return name
1418
 
        return address
 
1444
        return self.authors(rev, 'first', short=True, sep=', ')
 
1445
 
 
1446
    def authors(self, rev, who, short=False, sep=None):
 
1447
        """Generate list of authors, taking --authors option into account.
 
1448
 
 
1449
        The caller has to specify the name of a author list handler,
 
1450
        as provided by the author list registry, using the ``who``
 
1451
        argument.  That name only sets a default, though: when the
 
1452
        user selected a different author list generation using the
 
1453
        ``--authors`` command line switch, as represented by the
 
1454
        ``author_list_handler`` constructor argument, that value takes
 
1455
        precedence.
 
1456
 
 
1457
        :param rev: The revision for which to generate the list of authors.
 
1458
        :param who: Name of the default handler.
 
1459
        :param short: Whether to shorten names to either name or address.
 
1460
        :param sep: What separator to use for automatic concatenation.
 
1461
        """
 
1462
        if self._author_list_handler is not None:
 
1463
            # The user did specify --authors, which overrides the default
 
1464
            author_list_handler = self._author_list_handler
 
1465
        else:
 
1466
            # The user didn't specify --authors, so we use the caller's default
 
1467
            author_list_handler = author_list_registry.get(who)
 
1468
        names = author_list_handler(rev)
 
1469
        if short:
 
1470
            for i in range(len(names)):
 
1471
                name, address = config.parse_username(names[i])
 
1472
                if name:
 
1473
                    names[i] = name
 
1474
                else:
 
1475
                    names[i] = address
 
1476
        if sep is not None:
 
1477
            names = sep.join(names)
 
1478
        return names
1419
1479
 
1420
1480
    def merge_marker(self, revision):
1421
1481
        """Get the merge marker to include in the output or '' if none."""
1516
1576
                self.merge_marker(revision)))
1517
1577
        if revision.tags:
1518
1578
            lines.append('tags: %s' % (', '.join(revision.tags)))
1519
 
        if self.show_ids:
 
1579
        if self.show_ids or revision.revno is None:
1520
1580
            lines.append('revision-id: %s' % (revision.rev.revision_id,))
 
1581
        if self.show_ids:
1521
1582
            for parent_id in revision.rev.parent_ids:
1522
1583
                lines.append('parent: %s' % (parent_id,))
1523
1584
        lines.extend(self.custom_properties(revision.rev))
1524
1585
 
1525
1586
        committer = revision.rev.committer
1526
 
        authors = revision.rev.get_apparent_authors()
 
1587
        authors = self.authors(revision.rev, 'all')
1527
1588
        if authors != [committer]:
1528
1589
            lines.append('author: %s' % (", ".join(authors),))
1529
1590
        lines.append('committer: %s' % (committer,))
1586
1647
        indent = '    ' * depth
1587
1648
        revno_width = self.revno_width_by_depth.get(depth)
1588
1649
        if revno_width is None:
1589
 
            if revision.revno.find('.') == -1:
 
1650
            if revision.revno is None or revision.revno.find('.') == -1:
1590
1651
                # mainline revno, e.g. 12345
1591
1652
                revno_width = 5
1592
1653
            else:
1600
1661
        if revision.tags:
1601
1662
            tags = ' {%s}' % (', '.join(revision.tags))
1602
1663
        to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1603
 
                revision.revno, self.short_author(revision.rev),
 
1664
                revision.revno or "", self.short_author(revision.rev),
1604
1665
                format_date(revision.rev.timestamp,
1605
1666
                            revision.rev.timezone or 0,
1606
1667
                            self.show_timezone, date_fmt="%Y-%m-%d",
1607
1668
                            show_offset=False),
1608
1669
                tags, self.merge_marker(revision)))
1609
1670
        self.show_properties(revision.rev, indent+offset)
1610
 
        if self.show_ids:
 
1671
        if self.show_ids or revision.revno is None:
1611
1672
            to_file.write(indent + offset + 'revision-id:%s\n'
1612
1673
                          % (revision.rev.revision_id,))
1613
1674
        if not revision.rev.message:
1703
1764
                               self.show_timezone,
1704
1765
                               date_fmt='%Y-%m-%d',
1705
1766
                               show_offset=False)
1706
 
        committer_str = revision.rev.get_apparent_authors()[0].replace (' <', '  <')
 
1767
        committer_str = self.authors(revision.rev, 'first', sep=', ')
 
1768
        committer_str = committer_str.replace(' <', '  <')
1707
1769
        to_file.write('%s  %s\n\n' % (date_str,committer_str))
1708
1770
 
1709
1771
        if revision.delta is not None and revision.delta.has_changed():
1774
1836
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
1775
1837
 
1776
1838
 
 
1839
def author_list_all(rev):
 
1840
    return rev.get_apparent_authors()[:]
 
1841
 
 
1842
 
 
1843
def author_list_first(rev):
 
1844
    lst = rev.get_apparent_authors()
 
1845
    try:
 
1846
        return [lst[0]]
 
1847
    except IndexError:
 
1848
        return []
 
1849
 
 
1850
 
 
1851
def author_list_committer(rev):
 
1852
    return [rev.committer]
 
1853
 
 
1854
 
 
1855
author_list_registry = registry.Registry()
 
1856
 
 
1857
author_list_registry.register('all', author_list_all,
 
1858
                              'All authors')
 
1859
 
 
1860
author_list_registry.register('first', author_list_first,
 
1861
                              'The first author')
 
1862
 
 
1863
author_list_registry.register('committer', author_list_committer,
 
1864
                              'The committer')
 
1865
 
 
1866
 
1777
1867
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1778
1868
    # deprecated; for compatibility
1779
1869
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1930
2020
        lf.log_revision(lr)
1931
2021
 
1932
2022
 
1933
 
def _get_info_for_log_files(revisionspec_list, file_list):
 
2023
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
1934
2024
    """Find file-ids and kinds given a list of files and a revision range.
1935
2025
 
1936
2026
    We search for files at the end of the range. If not found there,
1940
2030
    :param file_list: the list of paths given on the command line;
1941
2031
      the first of these can be a branch location or a file path,
1942
2032
      the remainder must be file paths
 
2033
    :param add_cleanup: When the branch returned is read locked,
 
2034
      an unlock call will be queued to the cleanup.
1943
2035
    :return: (branch, info_list, start_rev_info, end_rev_info) where
1944
2036
      info_list is a list of (relative_path, file_id, kind) tuples where
1945
2037
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
1946
2038
      branch will be read-locked.
1947
2039
    """
1948
 
    from builtins import _get_revision_range, safe_relpath_files
 
2040
    from builtins import _get_revision_range
1949
2041
    tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
1950
 
    b.lock_read()
 
2042
    add_cleanup(b.lock_read().unlock)
1951
2043
    # XXX: It's damn messy converting a list of paths to relative paths when
1952
2044
    # those paths might be deleted ones, they might be on a case-insensitive
1953
2045
    # filesystem and/or they might be in silly locations (like another branch).
1957
2049
    # case of running log in a nested directory, assuming paths beyond the
1958
2050
    # first one haven't been deleted ...
1959
2051
    if tree:
1960
 
        relpaths = [path] + safe_relpath_files(tree, file_list[1:])
 
2052
        relpaths = [path] + tree.safe_relpath_files(file_list[1:])
1961
2053
    else:
1962
2054
        relpaths = [path] + file_list[1:]
1963
2055
    info_list = []