85
83
get_terminal_encoding,
88
from breezy.sixish import (
95
def find_touching_revisions(branch, file_id):
92
def find_touching_revisions(repository, last_revision, last_tree, last_path):
96
93
"""Yield a description of revisions which affect the file_id.
98
95
Each returned element is (revno, revision_id, description)
103
100
TODO: Perhaps some way to limit this to only particular revisions,
104
101
or to traverse a non-mainline set of revisions?
109
graph = branch.repository.get_graph()
110
history = list(graph.iter_lefthand_ancestry(branch.last_revision(),
111
[_mod_revision.NULL_REVISION]))
112
for revision_id in reversed(history):
113
this_tree = branch.repository.revision_tree(revision_id)
115
this_path = this_tree.id2path(file_id)
116
except errors.NoSuchId:
117
this_verifier = this_path = None
119
this_verifier = this_tree.get_file_verifier(this_path, file_id)
103
last_verifier = last_tree.get_file_verifier(last_path)
104
graph = repository.get_graph()
105
history = list(graph.iter_lefthand_ancestry(last_revision, []))
107
for revision_id in history:
108
this_tree = repository.revision_tree(revision_id)
109
this_intertree = InterTree.get(this_tree, last_tree)
110
this_path = this_intertree.find_source_path(last_path)
121
112
# now we know how it was last time, and how it is in this revision.
122
113
# are those two states effectively the same or not?
124
if not this_verifier and not last_verifier:
125
# not present in either
127
elif this_verifier and not last_verifier:
128
yield revno, revision_id, "added " + this_path
129
elif not this_verifier and last_verifier:
131
yield revno, revision_id, "deleted " + last_path
114
if this_path is not None and last_path is None:
115
yield revno, revision_id, "deleted " + this_path
116
this_verifier = this_tree.get_file_verifier(this_path)
117
elif this_path is None and last_path is not None:
118
yield revno, revision_id, "added " + last_path
132
119
elif this_path != last_path:
133
yield revno, revision_id, ("renamed %s => %s" % (last_path, this_path))
134
elif (this_verifier != last_verifier):
135
yield revno, revision_id, "modified " + this_path
120
yield revno, revision_id, ("renamed %s => %s" % (this_path, last_path))
121
this_verifier = this_tree.get_file_verifier(this_path)
123
this_verifier = this_tree.get_file_verifier(this_path)
124
if (this_verifier != last_verifier):
125
yield revno, revision_id, "modified " + this_path
137
127
last_verifier = this_verifier
138
128
last_path = this_path
129
last_tree = this_tree
130
if last_path is None:
142
135
def show_log(branch,
144
specific_fileid=None,
146
138
direction='reverse',
147
139
start_revision=None,
182
171
:param match: Dictionary of search lists to use when matching revision
185
# Convert old-style parameters to new-style parameters
186
if specific_fileid is not None:
187
file_ids = [specific_fileid]
192
delta_type = 'partial'
196
177
delta_type = None
199
diff_type = 'partial'
183
if isinstance(start_revision, int):
185
start_revision = revisionspec.RevisionInfo(branch, start_revision)
186
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
187
raise errors.InvalidRevisionNumber(start_revision)
189
if isinstance(end_revision, int):
191
end_revision = revisionspec.RevisionInfo(branch, end_revision)
192
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
193
raise errors.InvalidRevisionNumber(end_revision)
195
if end_revision is not None and end_revision.revno == 0:
196
raise errors.InvalidRevisionNumber(end_revision.revno)
205
198
# Build the request and execute it
206
rqst = make_log_request_dict(direction=direction, specific_fileids=file_ids,
199
rqst = make_log_request_dict(
207
201
start_revision=start_revision, end_revision=end_revision,
208
202
limit=limit, message_search=search,
209
203
delta_type=delta_type, diff_type=diff_type)
376
370
if not isinstance(lf, LogFormatter):
377
371
warn("not a LogFormatter instance: %r" % lf)
379
self.branch.lock_read()
373
with self.branch.lock_read():
381
374
if getattr(lf, 'begin_log', None):
383
376
self._show_body(lf)
384
377
if getattr(lf, 'end_log', None):
389
380
def _show_body(self, lf):
390
381
"""Show the main log output.
456
447
for revs in revision_iterator:
457
448
for (rev_id, revno, merge_depth), rev, delta in revs:
458
449
# 0 levels means show everything; merge_depth counts from 0
459
if levels != 0 and merge_depth >= levels:
450
if (levels != 0 and merge_depth is not None and
451
merge_depth >= levels):
461
453
if omit_merges and len(rev.parent_ids) > 1:
470
462
signature = format_signature_validity(rev_id, self.branch)
473
yield LogRevision(rev, revno, merge_depth, delta,
466
rev, revno, merge_depth, delta,
474
467
self.rev_tag_dict.get(rev_id), diff, signature)
494
487
path_encoding = get_diff_header_encoding()
495
488
diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
496
new_label='', path_encoding=path_encoding)
489
new_label='', path_encoding=path_encoding)
497
490
return s.getvalue()
499
492
def _create_log_revision_iterator(self):
523
517
generate_merge_revisions = rqst.get('levels') != 1
524
518
delayed_graph_generation = not rqst.get('specific_fileids') and (
525
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
519
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
526
520
view_revisions = _calc_view_revisions(
527
521
self.branch, self.start_rev_id, self.end_rev_id,
528
522
rqst.get('direction'),
533
527
# Apply the other filters
534
528
return make_log_rev_iterator(self.branch, view_revisions,
535
rqst.get('delta_type'), rqst.get('match'),
536
file_ids=rqst.get('specific_fileids'),
537
direction=rqst.get('direction'))
529
rqst.get('delta_type'), rqst.get('match'),
530
file_ids=rqst.get('specific_fileids'),
531
direction=rqst.get('direction'))
539
533
def _log_revision_iterator_using_per_file_graph(self):
540
534
# Get the base revisions, filtering by the revision range.
548
542
if not isinstance(view_revisions, list):
549
543
view_revisions = list(view_revisions)
550
544
view_revisions = _filter_revisions_touching_file_id(self.branch,
551
rqst.get('specific_fileids')[0], view_revisions,
552
include_merges=rqst.get('levels') != 1)
545
rqst.get('specific_fileids')[
547
include_merges=rqst.get('levels') != 1)
553
548
return make_log_rev_iterator(self.branch, view_revisions,
554
rqst.get('delta_type'), rqst.get('match'))
549
rqst.get('delta_type'), rqst.get('match'))
557
552
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
569
564
'--exclude-common-ancestry requires two different revisions'))
570
565
if direction not in ('reverse', 'forward'):
571
566
raise ValueError(gettext('invalid direction %r') % direction)
572
br_revno, br_rev_id = branch.last_revision_info()
567
br_rev_id = branch.last_revision()
568
if br_rev_id == _mod_revision.NULL_REVISION:
576
571
if (end_rev_id and start_rev_id == end_rev_id
577
572
and (not generate_merge_revisions
578
573
or not _has_merges(branch, end_rev_id))):
579
574
# If a single revision is requested, check we can handle it
580
return _generate_one_revision(branch, end_rev_id, br_rev_id,
575
return _generate_one_revision(branch, end_rev_id, br_rev_id,
582
577
if not generate_merge_revisions:
584
579
# If we only want to see linear revisions, we can iterate ...
589
584
# ancestor of the end limit, check it before outputting anything
590
585
if (direction == 'forward'
591
586
or (start_rev_id and not _is_obvious_ancestor(
592
branch, start_rev_id, end_rev_id))):
593
iter_revs = list(iter_revs)
587
branch, start_rev_id, end_rev_id))):
588
iter_revs = list(iter_revs)
594
589
if direction == 'forward':
595
590
iter_revs = reversed(iter_revs)
628
623
initial_revisions = []
629
624
if delayed_graph_generation:
631
for rev_id, revno, depth in _linear_view_revisions(
632
branch, start_rev_id, end_rev_id, exclude_common_ancestry):
626
for rev_id, revno, depth in _linear_view_revisions(
627
branch, start_rev_id, end_rev_id, exclude_common_ancestry):
633
628
if _has_merges(branch, rev_id):
634
629
# The end_rev_id can be nested down somewhere. We need an
635
630
# explicit ancestry check. There is an ambiguity here as we
642
637
# -- vila 20100319
643
638
graph = branch.repository.get_graph()
644
639
if (start_rev_id is not None
645
and not graph.is_ancestor(start_rev_id, end_rev_id)):
640
and not graph.is_ancestor(start_rev_id, end_rev_id)):
646
641
raise _StartNotLinearAncestor()
647
642
# Since we collected the revisions so far, we need to
648
643
# adjust end_rev_id.
657
652
# A merge was never detected so the lower revision limit can't
658
653
# be nested down somewhere
659
654
raise errors.BzrCommandError(gettext('Start revision not found in'
660
' history of end revision.'))
655
' history of end revision.'))
662
657
# We exit the loop above because we encounter a revision with merges, from
663
658
# this revision, we need to switch to _graph_view_revisions.
668
663
# make forward the exact opposite display, but showing the merge revisions
669
664
# indented at the end seems slightly nicer in that case.
670
665
view_revisions = itertools.chain(iter(initial_revisions),
671
_graph_view_revisions(branch, start_rev_id, end_rev_id,
672
rebase_initial_depths=(direction == 'reverse'),
673
exclude_common_ancestry=exclude_common_ancestry))
666
_graph_view_revisions(branch, start_rev_id, end_rev_id,
667
rebase_initial_depths=(
668
direction == 'reverse'),
669
exclude_common_ancestry=exclude_common_ancestry))
674
670
return view_revisions
732
728
:raises _StartNotLinearAncestor: if a start_rev_id is specified but
733
729
is not found walking the left-hand history
735
br_revno, br_rev_id = branch.last_revision_info()
736
731
repo = branch.repository
737
732
graph = repo.get_graph()
738
733
if start_rev_id is None and end_rev_id is None:
734
if branch._format.stores_revno() or \
735
config.GlobalStack().get('calculate_revnos'):
737
br_revno, br_rev_id = branch.last_revision_info()
738
except errors.GhostRevisionsHaveNoRevno:
739
br_rev_id = branch.last_revision()
744
br_rev_id = branch.last_revision()
740
747
graph_iter = graph.iter_lefthand_ancestry(br_rev_id,
741
(_mod_revision.NULL_REVISION,))
748
(_mod_revision.NULL_REVISION,))
744
751
revision_id = next(graph_iter)
747
754
yield e.revision_id, None, None
756
except StopIteration:
750
yield revision_id, str(cur_revno), 0
759
yield revision_id, str(cur_revno) if cur_revno is not None else None, 0
760
if cur_revno is not None:
763
br_rev_id = branch.last_revision()
753
764
if end_rev_id is None:
754
765
end_rev_id = br_rev_id
755
766
found_start = start_rev_id is None
756
767
graph_iter = graph.iter_lefthand_ancestry(end_rev_id,
757
(_mod_revision.NULL_REVISION,))
768
(_mod_revision.NULL_REVISION,))
760
771
revision_id = next(graph_iter)
827
838
if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
828
839
min_depth = min([d for r, n, d in view_revisions])
829
840
if min_depth != 0:
830
view_revisions = [(r, n, d-min_depth) for r, n, d in view_revisions]
841
view_revisions = [(r, n, d - min_depth)
842
for r, n, d in view_revisions]
831
843
return view_revisions
834
846
def make_log_rev_iterator(branch, view_revisions, generate_delta, search,
835
file_ids=None, direction='reverse'):
847
file_ids=None, direction='reverse'):
836
848
"""Create a revision iterator for log.
838
850
:param branch: The branch being logged.
862
874
# It would be nicer if log adapters were first class objects
863
875
# with custom parameters. This will do for now. IGC 20090127
864
876
if adapter == _make_delta_filter:
865
log_rev_iterator = adapter(branch, generate_delta,
866
search, log_rev_iterator, file_ids, direction)
877
log_rev_iterator = adapter(
878
branch, generate_delta, search, log_rev_iterator, file_ids,
868
log_rev_iterator = adapter(branch, generate_delta,
869
search, log_rev_iterator)
881
log_rev_iterator = adapter(
882
branch, generate_delta, search, log_rev_iterator)
870
883
return log_rev_iterator
888
901
return log_rev_iterator
889
searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
902
# Use lazy_compile so mapping to InvalidPattern error occurs.
903
searchRE = [(k, [lazy_regex.lazy_compile(x, re.IGNORECASE) for x in v])
890
904
for k, v in match.items()]
891
905
return _filter_re(searchRE, log_rev_iterator)
900
915
def _match_filter(searchRE, rev):
902
'message': (rev.message,),
903
'committer': (rev.committer,),
904
'author': (rev.get_apparent_authors()),
905
'bugs': list(rev.iter_bugs())
917
'message': (rev.message,),
918
'committer': (rev.committer,),
919
'author': (rev.get_apparent_authors()),
920
'bugs': list(rev.iter_bugs())
907
922
strings[''] = [item for inner_list in strings.values()
908
923
for item in inner_list]
909
for (k, v) in searchRE:
924
for k, v in searchRE:
910
925
if k in strings and not _match_any_filter(strings[k], v):
914
930
def _match_any_filter(strings, res):
915
return any(re.search(s) for re in res for s in strings)
931
return any(r.search(s) for r in res for s in strings)
917
934
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
918
fileids=None, direction='reverse'):
935
fileids=None, direction='reverse'):
919
936
"""Add revision deltas to a log iterator if needed.
921
938
:param branch: The branch being logged.
933
950
if not generate_delta and not fileids:
934
951
return log_rev_iterator
935
952
return _generate_deltas(branch.repository, log_rev_iterator,
936
generate_delta, fileids, direction)
953
generate_delta, fileids, direction)
939
956
def _generate_deltas(repository, log_rev_iterator, delta_type, fileids,
941
958
"""Create deltas for each batch of revisions in log_rev_iterator.
943
960
If we're only generating deltas for the sake of filtering against
997
1014
fileids set once their add or remove entry is detected respectively
999
1016
if stop_on == 'add':
1000
for item in delta.added:
1001
if item[1] in fileids:
1002
fileids.remove(item[1])
1017
for item in delta.added + delta.copied:
1018
if item.file_id in fileids:
1019
fileids.remove(item.file_id)
1003
1020
elif stop_on == 'delete':
1004
1021
for item in delta.removed:
1005
if item[1] in fileids:
1006
fileids.remove(item[1])
1022
if item.file_id in fileids:
1023
fileids.remove(item.file_id)
1009
1026
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
1063
1079
:return: (start_rev_id, end_rev_id) tuple.
1065
branch_revno, branch_rev_id = branch.last_revision_info()
1066
1081
start_rev_id = None
1067
if start_revision is None:
1083
if start_revision is not None:
1084
if not isinstance(start_revision, revisionspec.RevisionInfo):
1085
raise TypeError(start_revision)
1086
start_rev_id = start_revision.rev_id
1087
start_revno = start_revision.revno
1088
if start_revno is None:
1068
1089
start_revno = 1
1070
if isinstance(start_revision, revisionspec.RevisionInfo):
1071
start_rev_id = start_revision.rev_id
1072
start_revno = start_revision.revno or 1
1074
branch.check_real_revno(start_revision)
1075
start_revno = start_revision
1076
start_rev_id = branch.get_rev_id(start_revno)
1078
1091
end_rev_id = None
1079
if end_revision is None:
1080
end_revno = branch_revno
1082
if isinstance(end_revision, revisionspec.RevisionInfo):
1083
end_rev_id = end_revision.rev_id
1084
end_revno = end_revision.revno or branch_revno
1086
branch.check_real_revno(end_revision)
1087
end_revno = end_revision
1088
end_rev_id = branch.get_rev_id(end_revno)
1093
if end_revision is not None:
1094
if not isinstance(end_revision, revisionspec.RevisionInfo):
1095
raise TypeError(start_revision)
1096
end_rev_id = end_revision.rev_id
1097
end_revno = end_revision.revno
1090
if branch_revno != 0:
1099
if branch.last_revision() != _mod_revision.NULL_REVISION:
1091
1100
if (start_rev_id == _mod_revision.NULL_REVISION
1092
or end_rev_id == _mod_revision.NULL_REVISION):
1093
raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1094
if start_revno > end_revno:
1095
raise errors.BzrCommandError(gettext("Start revision must be "
1096
"older than the end revision."))
1101
or end_rev_id == _mod_revision.NULL_REVISION):
1102
raise errors.BzrCommandError(
1103
gettext('Logging revision 0 is invalid.'))
1104
if end_revno is not None and start_revno > end_revno:
1105
raise errors.BzrCommandError(
1106
gettext("Start revision must be older than the end revision."))
1097
1107
return (start_rev_id, end_rev_id)
1147
1157
end_revno = end_revision
1149
1159
if ((start_rev_id == _mod_revision.NULL_REVISION)
1150
or (end_rev_id == _mod_revision.NULL_REVISION)):
1160
or (end_rev_id == _mod_revision.NULL_REVISION)):
1151
1161
raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1152
1162
if start_revno > end_revno:
1153
1163
raise errors.BzrCommandError(gettext("Start revision must be older "
1154
"than the end revision."))
1164
"than the end revision."))
1156
1166
if end_revno < start_revno:
1157
1167
return None, None, None, None
1382
1392
self.to_file = to_file
1383
1393
# 'exact' stream used to show diff, it should print content 'as is'
1384
# and should not try to decode/encode it to unicode to avoid bug #328007
1394
# and should not try to decode/encode it to unicode to avoid bug
1385
1396
if to_exact_file is not None:
1386
1397
self.to_exact_file = to_exact_file
1388
# XXX: somewhat hacky; this assumes it's a codec writer; it's better
1389
# for code that expects to get diffs to pass in the exact file
1399
# XXX: somewhat hacky; this assumes it's a codec writer; it's
1400
# better for code that expects to get diffs to pass in the exact
1391
1402
self.to_exact_file = getattr(to_file, 'stream', to_file)
1392
1403
self.show_ids = show_ids
1393
1404
self.show_timezone = show_timezone
1394
1405
if delta_format is None:
1395
1406
# Ensures backward compatibility
1396
delta_format = 2 # long format
1407
delta_format = 2 # long format
1397
1408
self.delta_format = delta_format
1398
1409
self.levels = levels
1399
1410
self._show_advice = show_advice
1498
1509
lines = self._foreign_info_properties(revision)
1499
1510
for key, handler in properties_handler_registry.iteritems():
1500
lines.extend(self._format_properties(handler(revision)))
1512
lines.extend(self._format_properties(handler(revision)))
1514
trace.log_exception_quietly()
1515
trace.print_exception(sys.exc_info(), self.to_file)
1503
1518
def _foreign_info_properties(self, rev):
1533
1548
def show_diff(self, to_file, diff, indent):
1534
for l in diff.rstrip().split('\n'):
1535
to_file.write(indent + '%s\n' % (l,))
1549
encoding = get_terminal_encoding()
1550
for l in diff.rstrip().split(b'\n'):
1551
to_file.write(indent + l.decode(encoding, 'ignore') + '\n')
1538
1554
# Separator between revisions in long format
1569
1585
lines = [_LONG_SEP]
1570
1586
if revision.revno is not None:
1571
1587
lines.append('revno: %s%s' % (revision.revno,
1572
self.merge_marker(revision)))
1588
self.merge_marker(revision)))
1573
1589
if revision.tags:
1574
lines.append('tags: %s' % (', '.join(revision.tags)))
1590
lines.append('tags: %s' % (', '.join(sorted(revision.tags))))
1575
1591
if self.show_ids or revision.revno is None:
1576
lines.append('revision-id: %s' % (revision.rev.revision_id,))
1592
lines.append('revision-id: %s' %
1593
(revision.rev.revision_id.decode('utf-8'),))
1577
1594
if self.show_ids:
1578
1595
for parent_id in revision.rev.parent_ids:
1579
lines.append('parent: %s' % (parent_id,))
1596
lines.append('parent: %s' % (parent_id.decode('utf-8'),))
1580
1597
lines.extend(self.custom_properties(revision.rev))
1582
1599
committer = revision.rev.committer
1658
1675
to_file = self.to_file
1660
1677
if revision.tags:
1661
tags = ' {%s}' % (', '.join(revision.tags))
1678
tags = ' {%s}' % (', '.join(sorted(revision.tags)))
1662
1679
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1663
revision.revno or "", self.short_author(revision.rev),
1664
format_date(revision.rev.timestamp,
1665
revision.rev.timezone or 0,
1666
self.show_timezone, date_fmt="%Y-%m-%d",
1668
tags, self.merge_marker(revision)))
1669
self.show_properties(revision.rev, indent+offset)
1680
revision.revno or "", self.short_author(
1682
format_date(revision.rev.timestamp,
1683
revision.rev.timezone or 0,
1684
self.show_timezone, date_fmt="%Y-%m-%d",
1686
tags, self.merge_marker(revision)))
1687
self.show_properties(revision.rev, indent + offset)
1670
1688
if self.show_ids or revision.revno is None:
1671
1689
to_file.write(indent + offset + 'revision-id:%s\n'
1672
% (revision.rev.revision_id,))
1690
% (revision.rev.revision_id.decode('utf-8'),))
1673
1691
if not revision.rev.message:
1674
1692
to_file.write(indent + offset + '(no message)\n')
1681
1699
# Use the standard status output to display changes
1682
1700
from breezy.delta import report_delta
1683
1701
report_delta(to_file, revision.delta,
1684
short_status=self.delta_format==1,
1702
short_status=self.delta_format == 1,
1685
1703
show_ids=self.show_ids, indent=indent + offset)
1686
1704
if revision.diff is not None:
1687
1705
self.show_diff(self.to_exact_file, revision.diff, ' ')
1705
1723
def truncate(self, str, max_len):
1706
1724
if max_len is None or len(str) <= max_len:
1708
return str[:max_len-3] + '...'
1726
return str[:max_len - 3] + '...'
1710
1728
def date_string(self, rev):
1711
1729
return format_date(rev.timestamp, rev.timezone or 0,
1721
1739
def log_revision(self, revision):
1722
1740
indent = ' ' * revision.merge_depth
1723
1741
self.to_file.write(self.log_string(revision.revno, revision.rev,
1724
self._max_chars, revision.tags, indent))
1742
self._max_chars, revision.tags, indent))
1725
1743
self.to_file.write('\n')
1727
1745
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1740
1758
# show revno only when is not None
1741
1759
out.append("%s:" % revno)
1742
1760
if max_chars is not None:
1743
out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1761
out.append(self.truncate(
1762
self.short_author(rev), (max_chars + 3) // 4))
1745
1764
out.append(self.short_author(rev))
1746
1765
out.append(self.date_string(rev))
1747
1766
if len(rev.parent_ids) > 1:
1748
1767
out.append('[merge]')
1750
tag_str = '{%s}' % (', '.join(tags))
1769
tag_str = '{%s}' % (', '.join(sorted(tags)))
1751
1770
out.append(tag_str)
1752
1771
out.append(rev.get_summary())
1753
1772
return self.truncate(prefix + " ".join(out).rstrip('\n'), max_chars)
1774
1793
if revision.delta is not None and revision.delta.has_changed():
1775
1794
for c in revision.delta.added + revision.delta.removed + revision.delta.modified:
1795
if c.path[0] is None:
1777
1799
to_file.write('\t* %s:\n' % (path,))
1778
for c in revision.delta.renamed:
1779
oldpath, newpath = c[:2]
1800
for c in revision.delta.renamed + revision.delta.copied:
1780
1801
# For renamed files, show both the old and the new path
1781
to_file.write('\t* %s:\n\t* %s:\n' % (oldpath, newpath))
1802
to_file.write('\t* %s:\n\t* %s:\n' % (c.path[0], c.path[1]))
1782
1803
to_file.write('\n')
1784
1805
if not revision.rev.message:
1891
1913
for i in range(max(len(new_rh), len(old_rh))):
1892
1914
if (len(new_rh) <= i
1893
1915
or len(old_rh) <= i
1894
or new_rh[i] != old_rh[i]):
1916
or new_rh[i] != old_rh[i]):
1898
1920
if base_idx is None:
1899
1921
to_file.write('Nothing seems to have changed\n')
1901
## TODO: It might be nice to do something like show_log
1902
## and show the merged entries. But since this is the
1903
## removed revisions, it shouldn't be as important
1923
# TODO: It might be nice to do something like show_log
1924
# and show the merged entries. But since this is the
1925
# removed revisions, it shouldn't be as important
1904
1926
if base_idx < len(old_rh):
1905
to_file.write('*'*60)
1927
to_file.write('*' * 60)
1906
1928
to_file.write('\nRemoved Revisions:\n')
1907
1929
for i in range(base_idx, len(old_rh)):
1908
1930
rev = branch.repository.get_revision(old_rh[i])
1909
lr = LogRevision(rev, i+1, 0, None)
1931
lr = LogRevision(rev, i + 1, 0, None)
1910
1932
lf.log_revision(lr)
1911
to_file.write('*'*60)
1933
to_file.write('*' * 60)
1912
1934
to_file.write('\n\n')
1913
1935
if base_idx < len(new_rh):
1914
1936
to_file.write('Added Revisions:\n')
1915
1937
show_log(branch,
1919
1940
direction='forward',
1920
start_revision=base_idx+1,
1941
start_revision=base_idx + 1,
1921
1942
end_revision=len(new_rh),
1991
2012
log_format = log_formatter_registry.get_default(branch)
1992
2013
lf = log_format(show_ids=False, to_file=output, show_timezone='original')
1993
2014
if old_history != []:
1994
output.write('*'*60)
2015
output.write('*' * 60)
1995
2016
output.write('\nRemoved Revisions:\n')
1996
2017
show_flat_log(branch.repository, old_history, old_revno, lf)
1997
output.write('*'*60)
2018
output.write('*' * 60)
1998
2019
output.write('\n\n')
1999
2020
if new_history != []:
2000
2021
output.write('Added Revisions:\n')
2001
2022
start_revno = new_revno - len(new_history) + 1
2002
show_log(branch, lf, None, verbose=False, direction='forward',
2003
start_revision=start_revno,)
2023
show_log(branch, lf, verbose=False, direction='forward',
2024
start_revision=start_revno)
2006
2027
def show_flat_log(repository, history, last_revno, lf):
2011
2032
:param last_revno: The revno of the last revision_id in the history.
2012
2033
:param lf: The log formatter to use.
2014
start_revno = last_revno - len(history) + 1
2015
2035
revisions = repository.get_revisions(history)
2016
2036
for i, rev in enumerate(revisions):
2017
2037
lr = LogRevision(rev, i + last_revno, 0, None)
2018
2038
lf.log_revision(lr)
2021
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
2041
def _get_info_for_log_files(revisionspec_list, file_list, exit_stack):
2022
2042
"""Find file-ids and kinds given a list of files and a revision range.
2024
2044
We search for files at the end of the range. If not found there,
2028
2048
:param file_list: the list of paths given on the command line;
2029
2049
the first of these can be a branch location or a file path,
2030
2050
the remainder must be file paths
2031
:param add_cleanup: When the branch returned is read locked,
2032
an unlock call will be queued to the cleanup.
2051
:param exit_stack: When the branch returned is read locked,
2052
an unlock call will be queued to the exit stack.
2033
2053
:return: (branch, info_list, start_rev_info, end_rev_info) where
2034
2054
info_list is a list of (relative_path, file_id, kind) tuples where
2035
2055
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2038
2058
from breezy.builtins import _get_revision_range
2039
2059
tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2041
add_cleanup(b.lock_read().unlock)
2061
exit_stack.enter_context(b.lock_read())
2042
2062
# XXX: It's damn messy converting a list of paths to relative paths when
2043
2063
# those paths might be deleted ones, they might be on a case-insensitive
2044
2064
# filesystem and/or they might be in silly locations (like another branch).
2053
2073
relpaths = [path] + file_list[1:]
2055
2075
start_rev_info, end_rev_info = _get_revision_range(revisionspec_list, b,
2057
2077
if relpaths in ([], [u'']):
2058
2078
return b, [], start_rev_info, end_rev_info
2059
2079
if start_rev_info is None and end_rev_info is None:
2124
2144
properties_handler_registry = registry.Registry()
2126
2146
# Use the properties handlers to print out bug information if available
2127
2149
def _bugs_properties_handler(revision):
2128
if 'bugs' in revision.properties:
2129
bug_lines = revision.properties['bugs'].split('\n')
2130
bug_rows = [line.split(' ', 1) for line in bug_lines]
2131
fixed_bug_urls = [row[0] for row in bug_rows if
2132
len(row) > 1 and row[1] == 'fixed']
2151
related_bug_urls = []
2152
for bug_url, status in revision.iter_bugs():
2153
if status == 'fixed':
2154
fixed_bug_urls.append(bug_url)
2155
elif status == 'related':
2156
related_bug_urls.append(bug_url)
2159
text = ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls))
2160
ret[text] = ' '.join(fixed_bug_urls)
2161
if related_bug_urls:
2162
text = ngettext('related bug', 'related bugs',
2163
len(related_bug_urls))
2164
ret[text] = ' '.join(related_bug_urls)
2135
return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
2136
' '.join(fixed_bug_urls)}
2139
2168
properties_handler_registry.register('bugs_properties_handler',
2140
2169
_bugs_properties_handler)