1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005-2011 Canonical Ltd
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
84
83
from bzrlib.osutils import (
86
85
format_date_with_offset_in_original_timezone,
86
get_diff_header_encoding,
87
87
get_terminal_encoding,
91
90
from bzrlib.symbol_versioning import (
433
432
specific_files = None
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='', path_encoding=path_encoding)
437
437
return s.getvalue()
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, exclude_common_ancestry)
526
526
if direction == 'forward':
527
527
iter_revs = reversed(iter_revs)
540
540
return [(br_rev_id, br_revno, 0)]
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)]
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:
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
628
def _compute_revno_str(branch, rev_id):
629
"""Compute the revno string from a rev_id.
631
:return: The revno string, or None if the revision is not in the supplied
635
revno = branch.revision_id_to_dotted_revno(rev_id)
636
except errors.NoSuchRevision:
637
# The revision must be outside of this branch
640
return '.'.join(str(n) for n in revno)
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)
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
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]
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.
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
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
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)
1169
1193
# Lookup all possible text keys to determine which ones actually modified
1195
graph = branch.repository.get_file_graph()
1196
get_parent_map = graph.get_parent_map
1171
1197
text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1172
1198
next_keys = None
1173
1199
# Looking up keys in batches of 1000 can cut the time in half, as well as
1177
1203
# indexing layer. We might consider passing in hints as to the known
1178
1204
# access pattern (sparse/clustered, high success rate/low success
1179
1205
# rate). This particular access is clustered with a low success rate.
1180
get_parent_map = branch.repository.texts.get_parent_map
1181
1206
modified_text_revisions = set()
1182
1207
chunk_size = 1000
1183
1208
for start in xrange(0, len(text_keys), chunk_size):
1312
1340
to indicate which LogRevision attributes it supports:
1314
1342
- supports_delta must be True if this log formatter supports delta.
1315
Otherwise the delta attribute may not be populated. The 'delta_format'
1316
attribute describes whether the 'short_status' format (1) or the long
1317
one (2) should be used.
1343
Otherwise the delta attribute may not be populated. The 'delta_format'
1344
attribute describes whether the 'short_status' format (1) or the long
1345
one (2) should be used.
1319
1347
- supports_merge_revisions must be True if this log formatter supports
1320
merge revisions. If not, then only mainline revisions will be passed
1348
merge revisions. If not, then only mainline revisions will be passed
1323
1351
- preferred_levels is the number of levels this formatter defaults to.
1324
The default value is zero meaning display all levels.
1325
This value is only relevant if supports_merge_revisions is True.
1352
The default value is zero meaning display all levels.
1353
This value is only relevant if supports_merge_revisions is True.
1327
1355
- supports_tags must be True if this log formatter supports tags.
1328
Otherwise the tags attribute may not be populated.
1356
Otherwise the tags attribute may not be populated.
1330
1358
- supports_diff must be True if this log formatter supports diffs.
1331
Otherwise the diff attribute may not be populated.
1359
Otherwise the diff attribute may not be populated.
1333
1361
Plugins can register functions to show custom revision properties using
1334
1362
the properties_handler_registry. The registered function
1335
must respect the following interface description:
1363
must respect the following interface description::
1336
1365
def my_show_properties(properties_dict):
1337
1366
# code that returns a dict {'name':'value'} of the properties
1550
1579
self.merge_marker(revision)))
1551
1580
if revision.tags:
1552
1581
lines.append('tags: %s' % (', '.join(revision.tags)))
1582
if self.show_ids or revision.revno is None:
1554
1583
lines.append('revision-id: %s' % (revision.rev.revision_id,))
1555
1585
for parent_id in revision.rev.parent_ids:
1556
1586
lines.append('parent: %s' % (parent_id,))
1557
1587
lines.extend(self.custom_properties(revision.rev))
1620
1650
indent = ' ' * depth
1621
1651
revno_width = self.revno_width_by_depth.get(depth)
1622
1652
if revno_width is None:
1623
if revision.revno.find('.') == -1:
1653
if revision.revno is None or revision.revno.find('.') == -1:
1624
1654
# mainline revno, e.g. 12345
1625
1655
revno_width = 5
1634
1664
if revision.tags:
1635
1665
tags = ' {%s}' % (', '.join(revision.tags))
1636
1666
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1637
revision.revno, self.short_author(revision.rev),
1667
revision.revno or "", self.short_author(revision.rev),
1638
1668
format_date(revision.rev.timestamp,
1639
1669
revision.rev.timezone or 0,
1640
1670
self.show_timezone, date_fmt="%Y-%m-%d",
1641
1671
show_offset=False),
1642
1672
tags, self.merge_marker(revision)))
1643
1673
self.show_properties(revision.rev, indent+offset)
1674
if self.show_ids or revision.revno is None:
1645
1675
to_file.write(indent + offset + 'revision-id:%s\n'
1646
1676
% (revision.rev.revision_id,))
1647
1677
if not revision.rev.message:
1701
1731
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1702
1732
"""Format log info into one string. Truncate tail of string
1703
:param revno: revision number or None.
1704
Revision numbers counts from 1.
1705
:param rev: revision object
1706
:param max_chars: maximum length of resulting string
1707
:param tags: list of tags or None
1708
:param prefix: string to prefix each line
1709
:return: formatted truncated string
1734
:param revno: revision number or None.
1735
Revision numbers counts from 1.
1736
:param rev: revision object
1737
:param max_chars: maximum length of resulting string
1738
:param tags: list of tags or None
1739
:param prefix: string to prefix each line
1740
:return: formatted truncated string
1713
1744
# show revno only when is not None
1714
1745
out.append("%s:" % revno)
1715
out.append(self.truncate(self.short_author(rev), 20))
1746
if max_chars is not None:
1747
out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1749
out.append(self.short_author(rev))
1716
1750
out.append(self.date_string(rev))
1717
1751
if len(rev.parent_ids) > 1:
1718
1752
out.append('[merge]')
2010
2044
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2011
2045
branch will be read-locked.
2013
from builtins import _get_revision_range, safe_relpath_files
2047
from builtins import _get_revision_range
2014
2048
tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2015
2049
add_cleanup(b.lock_read().unlock)
2016
2050
# XXX: It's damn messy converting a list of paths to relative paths when
2022
2056
# case of running log in a nested directory, assuming paths beyond the
2023
2057
# first one haven't been deleted ...
2025
relpaths = [path] + safe_relpath_files(tree, file_list[1:])
2059
relpaths = [path] + tree.safe_relpath_files(file_list[1:])
2027
2061
relpaths = [path] + file_list[1:]