92
from .tree import InterTree
93
from .tree import find_previous_path
95
96
def find_touching_revisions(repository, last_revision, last_tree, last_path):
109
110
revno = len(history)
110
111
for revision_id in history:
111
112
this_tree = repository.revision_tree(revision_id)
112
this_intertree = InterTree.get(this_tree, last_tree)
113
this_path = this_intertree.find_source_path(last_path)
113
this_path = find_previous_path(last_tree, this_tree, last_path)
115
115
# now we know how it was last time, and how it is in this revision.
116
116
# are those two states effectively the same or not?
155
156
:param lf: The LogFormatter object showing the output.
158
:param specific_fileid: If not None, list only the commits affecting the
159
specified file, rather than all commits.
157
161
:param verbose: If True show added/changed/deleted/renamed files.
159
163
:param direction: 'reverse' (default) is latest to earliest; 'forward' is
174
178
:param match: Dictionary of search lists to use when matching revision
181
# Convert old-style parameters to new-style parameters
182
if specific_fileid is not None:
183
file_ids = [specific_fileid]
188
delta_type = 'partial'
180
192
delta_type = None
195
diff_type = 'partial'
186
201
if isinstance(start_revision, int):
188
203
start_revision = revisionspec.RevisionInfo(branch, start_revision)
189
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
204
except errors.NoSuchRevision:
190
205
raise errors.InvalidRevisionNumber(start_revision)
192
207
if isinstance(end_revision, int):
194
209
end_revision = revisionspec.RevisionInfo(branch, end_revision)
195
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
210
except errors.NoSuchRevision:
196
211
raise errors.InvalidRevisionNumber(end_revision)
198
213
if end_revision is not None and end_revision.revno == 0:
199
214
raise errors.InvalidRevisionNumber(end_revision.revno)
201
216
# Build the request and execute it
202
rqst = make_log_request_dict(
217
rqst = make_log_request_dict(direction=direction, specific_fileids=file_ids,
204
218
start_revision=start_revision, end_revision=end_revision,
205
219
limit=limit, message_search=search,
206
220
delta_type=delta_type, diff_type=diff_type)
293
307
match['message'] = [message_search]
295
match = {'message': [message_search]}
309
match={ 'message': [message_search] }
297
311
'direction': direction,
298
312
'specific_fileids': specific_fileids,
408
422
lf.log_revision(lr)
409
423
except errors.GhostRevisionUnusableHere:
410
424
raise errors.BzrCommandError(
411
gettext('Further revision history missing.'))
425
gettext('Further revision history missing.'))
414
428
def _generator_factory(self, branch, rqst):
450
464
for revs in revision_iterator:
451
465
for (rev_id, revno, merge_depth), rev, delta in revs:
452
466
# 0 levels means show everything; merge_depth counts from 0
453
if (levels != 0 and merge_depth is not None and
454
merge_depth >= levels):
467
if levels != 0 and merge_depth >= levels:
456
469
if omit_merges and len(rev.parent_ids) > 1:
465
478
signature = format_signature_validity(rev_id, self.branch)
469
rev, revno, merge_depth, delta,
481
yield LogRevision(rev, revno, merge_depth, delta,
470
482
self.rev_tag_dict.get(rev_id), diff, signature)
490
502
path_encoding = get_diff_header_encoding()
491
503
diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
492
new_label='', path_encoding=path_encoding)
504
new_label='', path_encoding=path_encoding)
493
505
return s.getvalue()
495
507
def _create_log_revision_iterator(self):
509
521
# not a directory
510
522
file_count = len(self.rqst.get('specific_fileids'))
511
523
if file_count != 1:
512
raise errors.BzrError(
513
"illegal LogRequest: must match-using-deltas "
524
raise BzrError("illegal LogRequest: must match-using-deltas "
514
525
"when logging %d files" % file_count)
515
526
return self._log_revision_iterator_using_per_file_graph()
520
531
generate_merge_revisions = rqst.get('levels') != 1
521
532
delayed_graph_generation = not rqst.get('specific_fileids') and (
522
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
533
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
523
534
view_revisions = _calc_view_revisions(
524
535
self.branch, self.start_rev_id, self.end_rev_id,
525
536
rqst.get('direction'),
530
541
# Apply the other filters
531
542
return make_log_rev_iterator(self.branch, view_revisions,
532
rqst.get('delta_type'), rqst.get('match'),
533
file_ids=rqst.get('specific_fileids'),
534
direction=rqst.get('direction'))
543
rqst.get('delta_type'), rqst.get('match'),
544
file_ids=rqst.get('specific_fileids'),
545
direction=rqst.get('direction'))
536
547
def _log_revision_iterator_using_per_file_graph(self):
537
548
# Get the base revisions, filtering by the revision range.
545
556
if not isinstance(view_revisions, list):
546
557
view_revisions = list(view_revisions)
547
558
view_revisions = _filter_revisions_touching_file_id(self.branch,
548
rqst.get('specific_fileids')[
550
include_merges=rqst.get('levels') != 1)
559
rqst.get('specific_fileids')[0], view_revisions,
560
include_merges=rqst.get('levels') != 1)
551
561
return make_log_rev_iterator(self.branch, view_revisions,
552
rqst.get('delta_type'), rqst.get('match'))
562
rqst.get('delta_type'), rqst.get('match'))
555
565
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
575
585
and (not generate_merge_revisions
576
586
or not _has_merges(branch, end_rev_id))):
577
587
# If a single revision is requested, check we can handle it
578
return _generate_one_revision(branch, end_rev_id, br_rev_id,
588
return _generate_one_revision(branch, end_rev_id, br_rev_id,
580
590
if not generate_merge_revisions:
582
592
# If we only want to see linear revisions, we can iterate ...
587
597
# ancestor of the end limit, check it before outputting anything
588
598
if (direction == 'forward'
589
599
or (start_rev_id and not _is_obvious_ancestor(
590
branch, start_rev_id, end_rev_id))):
591
iter_revs = list(iter_revs)
600
branch, start_rev_id, end_rev_id))):
601
iter_revs = list(iter_revs)
592
602
if direction == 'forward':
593
603
iter_revs = reversed(iter_revs)
626
636
initial_revisions = []
627
637
if delayed_graph_generation:
629
for rev_id, revno, depth in _linear_view_revisions(
630
branch, start_rev_id, end_rev_id, exclude_common_ancestry):
639
for rev_id, revno, depth in _linear_view_revisions(
640
branch, start_rev_id, end_rev_id, exclude_common_ancestry):
631
641
if _has_merges(branch, rev_id):
632
642
# The end_rev_id can be nested down somewhere. We need an
633
643
# explicit ancestry check. There is an ambiguity here as we
640
650
# -- vila 20100319
641
651
graph = branch.repository.get_graph()
642
652
if (start_rev_id is not None
643
and not graph.is_ancestor(start_rev_id, end_rev_id)):
653
and not graph.is_ancestor(start_rev_id, end_rev_id)):
644
654
raise _StartNotLinearAncestor()
645
655
# Since we collected the revisions so far, we need to
646
656
# adjust end_rev_id.
655
665
# A merge was never detected so the lower revision limit can't
656
666
# be nested down somewhere
657
667
raise errors.BzrCommandError(gettext('Start revision not found in'
658
' history of end revision.'))
668
' history of end revision.'))
660
670
# We exit the loop above because we encounter a revision with merges, from
661
671
# this revision, we need to switch to _graph_view_revisions.
666
676
# make forward the exact opposite display, but showing the merge revisions
667
677
# indented at the end seems slightly nicer in that case.
668
678
view_revisions = itertools.chain(iter(initial_revisions),
669
_graph_view_revisions(branch, start_rev_id, end_rev_id,
670
rebase_initial_depths=(
671
direction == 'reverse'),
672
exclude_common_ancestry=exclude_common_ancestry))
679
_graph_view_revisions(branch, start_rev_id, end_rev_id,
680
rebase_initial_depths=(direction == 'reverse'),
681
exclude_common_ancestry=exclude_common_ancestry))
673
682
return view_revisions
707
716
# both on mainline
708
717
return start_dotted[0] <= end_dotted[0]
709
718
elif (len(start_dotted) == 3 and len(end_dotted) == 3 and
710
start_dotted[0:1] == end_dotted[0:1]):
719
start_dotted[0:1] == end_dotted[0:1]):
711
720
# both on same development line
712
721
return start_dotted[2] <= end_dotted[2]
734
743
repo = branch.repository
735
744
graph = repo.get_graph()
736
745
if start_rev_id is None and end_rev_id is None:
737
if branch._format.stores_revno() or \
738
config.GlobalStack().get('calculate_revnos'):
740
br_revno, br_rev_id = branch.last_revision_info()
741
except errors.GhostRevisionsHaveNoRevno:
742
br_rev_id = branch.last_revision()
747
br_revno, br_rev_id = branch.last_revision_info()
748
except errors.GhostRevisionsHaveNoRevno:
747
749
br_rev_id = branch.last_revision()
750
753
graph_iter = graph.iter_lefthand_ancestry(br_rev_id,
751
(_mod_revision.NULL_REVISION,))
754
(_mod_revision.NULL_REVISION,))
754
757
revision_id = next(graph_iter)
757
760
yield e.revision_id, None, None
759
except StopIteration:
762
763
yield revision_id, str(cur_revno) if cur_revno is not None else None, 0
763
764
if cur_revno is not None:
768
769
end_rev_id = br_rev_id
769
770
found_start = start_rev_id is None
770
771
graph_iter = graph.iter_lefthand_ancestry(end_rev_id,
771
(_mod_revision.NULL_REVISION,))
772
(_mod_revision.NULL_REVISION,))
774
775
revision_id = next(graph_iter)
841
842
if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
842
843
min_depth = min([d for r, n, d in view_revisions])
843
844
if min_depth != 0:
844
view_revisions = [(r, n, d - min_depth)
845
for r, n, d in view_revisions]
845
view_revisions = [(r, n, d-min_depth) for r, n, d in view_revisions]
846
846
return view_revisions
849
849
def make_log_rev_iterator(branch, view_revisions, generate_delta, search,
850
file_ids=None, direction='reverse'):
850
file_ids=None, direction='reverse'):
851
851
"""Create a revision iterator for log.
853
853
:param branch: The branch being logged.
877
877
# It would be nicer if log adapters were first class objects
878
878
# with custom parameters. This will do for now. IGC 20090127
879
879
if adapter == _make_delta_filter:
880
log_rev_iterator = adapter(
881
branch, generate_delta, search, log_rev_iterator, file_ids,
880
log_rev_iterator = adapter(branch, generate_delta,
881
search, log_rev_iterator, file_ids, direction)
884
log_rev_iterator = adapter(
885
branch, generate_delta, search, log_rev_iterator)
883
log_rev_iterator = adapter(branch, generate_delta,
884
search, log_rev_iterator)
886
885
return log_rev_iterator
904
903
return log_rev_iterator
905
# Use lazy_compile so mapping to InvalidPattern error occurs.
906
searchRE = [(k, [lazy_regex.lazy_compile(x, re.IGNORECASE) for x in v])
904
searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
907
905
for k, v in match.items()]
908
906
return _filter_re(searchRE, log_rev_iterator)
918
915
def _match_filter(searchRE, rev):
920
'message': (rev.message,),
921
'committer': (rev.committer,),
922
'author': (rev.get_apparent_authors()),
923
'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())
925
922
strings[''] = [item for inner_list in strings.values()
926
923
for item in inner_list]
927
for k, v in searchRE:
924
for (k, v) in searchRE:
928
925
if k in strings and not _match_any_filter(strings[k], v):
933
929
def _match_any_filter(strings, res):
934
return any(r.search(s) for r in res for s in strings)
930
return any(re.search(s) for re in res for s in strings)
937
932
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
938
fileids=None, direction='reverse'):
933
fileids=None, direction='reverse'):
939
934
"""Add revision deltas to a log iterator if needed.
941
936
:param branch: The branch being logged.
953
948
if not generate_delta and not fileids:
954
949
return log_rev_iterator
955
950
return _generate_deltas(branch.repository, log_rev_iterator,
956
generate_delta, fileids, direction)
951
generate_delta, fileids, direction)
959
954
def _generate_deltas(repository, log_rev_iterator, delta_type, fileids,
961
956
"""Create deltas for each batch of revisions in log_rev_iterator.
963
958
If we're only generating deltas for the sake of filtering against
1017
1012
fileids set once their add or remove entry is detected respectively
1019
1014
if stop_on == 'add':
1020
for item in delta.added + delta.copied:
1021
if item.file_id in fileids:
1022
fileids.remove(item.file_id)
1015
for item in delta.added:
1016
if item[1] in fileids:
1017
fileids.remove(item[1])
1023
1018
elif stop_on == 'delete':
1024
1019
for item in delta.removed:
1025
if item.file_id in fileids:
1026
fileids.remove(item.file_id)
1020
if item[1] in fileids:
1021
fileids.remove(item[1])
1029
1024
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
1098
1093
raise TypeError(start_revision)
1099
1094
end_rev_id = end_revision.rev_id
1100
1095
end_revno = end_revision.revno
1096
if end_revno is None:
1098
end_revno = branch.revno()
1099
except errors.GhostRevisionsHaveNoRevno:
1102
1102
if branch.last_revision() != _mod_revision.NULL_REVISION:
1103
1103
if (start_rev_id == _mod_revision.NULL_REVISION
1104
or end_rev_id == _mod_revision.NULL_REVISION):
1105
raise errors.BzrCommandError(
1106
gettext('Logging revision 0 is invalid.'))
1104
or end_rev_id == _mod_revision.NULL_REVISION):
1105
raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1107
1106
if end_revno is not None and start_revno > end_revno:
1108
raise errors.BzrCommandError(
1109
gettext("Start revision must be older than the end revision."))
1107
raise errors.BzrCommandError(gettext("Start revision must be "
1108
"older than the end revision."))
1110
1109
return (start_rev_id, end_rev_id)
1160
1159
end_revno = end_revision
1162
1161
if ((start_rev_id == _mod_revision.NULL_REVISION)
1163
or (end_rev_id == _mod_revision.NULL_REVISION)):
1162
or (end_rev_id == _mod_revision.NULL_REVISION)):
1164
1163
raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1165
1164
if start_revno > end_revno:
1166
1165
raise errors.BzrCommandError(gettext("Start revision must be older "
1167
"than the end revision."))
1166
"than the end revision."))
1169
1168
if end_revno < start_revno:
1170
1169
return None, None, None, None
1196
1195
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1197
include_merges=True):
1196
include_merges=True):
1198
1197
r"""Return the list of revision ids which touch a given file id.
1200
1199
The function filters view_revisions and returns a subset.
1281
1280
"""Reverse revisions by depth.
1283
1282
Revisions with a different depth are sorted as a group with the previous
1284
revision of that depth. There may be no topological justification for this
1283
revision of that depth. There may be no topological justification for this,
1285
1284
but it looks much nicer.
1287
1286
# Add a fake revision at start so that we can always attach sub revisions
1395
1394
self.to_file = to_file
1396
1395
# 'exact' stream used to show diff, it should print content 'as is'
1397
# and should not try to decode/encode it to unicode to avoid bug
1396
# and should not try to decode/encode it to unicode to avoid bug #328007
1399
1397
if to_exact_file is not None:
1400
1398
self.to_exact_file = to_exact_file
1402
# XXX: somewhat hacky; this assumes it's a codec writer; it's
1403
# better for code that expects to get diffs to pass in the exact
1400
# XXX: somewhat hacky; this assumes it's a codec writer; it's better
1401
# for code that expects to get diffs to pass in the exact file
1405
1403
self.to_exact_file = getattr(to_file, 'stream', to_file)
1406
1404
self.show_ids = show_ids
1407
1405
self.show_timezone = show_timezone
1408
1406
if delta_format is None:
1409
1407
# Ensures backward compatibility
1410
delta_format = 2 # long format
1408
delta_format = 2 # long format
1411
1409
self.delta_format = delta_format
1412
1410
self.levels = levels
1413
1411
self._show_advice = show_advice
1512
1510
lines = self._foreign_info_properties(revision)
1513
1511
for key, handler in properties_handler_registry.iteritems():
1515
lines.extend(self._format_properties(handler(revision)))
1517
trace.log_exception_quietly()
1518
trace.print_exception(sys.exc_info(), self.to_file)
1512
lines.extend(self._format_properties(handler(revision)))
1521
1515
def _foreign_info_properties(self, rev):
1529
1523
rev.mapping.vcs.show_foreign_revid(rev.foreign_revid))
1531
1525
# Imported foreign revision revision ids always contain :
1532
if b":" not in rev.revision_id:
1526
if not b":" in rev.revision_id:
1535
1529
# Revision was once imported from a foreign repository
1551
1545
def show_diff(self, to_file, diff, indent):
1552
encoding = get_terminal_encoding()
1553
for l in diff.rstrip().split(b'\n'):
1554
to_file.write(indent + l.decode(encoding, 'ignore') + '\n')
1546
for l in diff.rstrip().split('\n'):
1547
to_file.write(indent + '%s\n' % (l,))
1557
1550
# Separator between revisions in long format
1581
1574
def _date_string_original_timezone(self, rev):
1582
1575
return format_date_with_offset_in_original_timezone(rev.timestamp,
1585
1578
def log_revision(self, revision):
1586
1579
"""Log a revision, either merged or not."""
1588
1581
lines = [_LONG_SEP]
1589
1582
if revision.revno is not None:
1590
1583
lines.append('revno: %s%s' % (revision.revno,
1591
self.merge_marker(revision)))
1584
self.merge_marker(revision)))
1592
1585
if revision.tags:
1593
lines.append('tags: %s' % (', '.join(sorted(revision.tags))))
1586
lines.append('tags: %s' % (', '.join(revision.tags)))
1594
1587
if self.show_ids or revision.revno is None:
1595
lines.append('revision-id: %s' %
1596
(revision.rev.revision_id.decode('utf-8'),))
1588
lines.append('revision-id: %s' % (revision.rev.revision_id,))
1597
1589
if self.show_ids:
1598
1590
for parent_id in revision.rev.parent_ids:
1599
lines.append('parent: %s' % (parent_id.decode('utf-8'),))
1591
lines.append('parent: %s' % (parent_id,))
1600
1592
lines.extend(self.custom_properties(revision.rev))
1602
1594
committer = revision.rev.committer
1678
1670
to_file = self.to_file
1680
1672
if revision.tags:
1681
tags = ' {%s}' % (', '.join(sorted(revision.tags)))
1673
tags = ' {%s}' % (', '.join(revision.tags))
1682
1674
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1683
revision.revno or "", self.short_author(
1685
format_date(revision.rev.timestamp,
1686
revision.rev.timezone or 0,
1687
self.show_timezone, date_fmt="%Y-%m-%d",
1689
tags, self.merge_marker(revision)))
1690
self.show_properties(revision.rev, indent + offset)
1675
revision.revno or "", self.short_author(revision.rev),
1676
format_date(revision.rev.timestamp,
1677
revision.rev.timezone or 0,
1678
self.show_timezone, date_fmt="%Y-%m-%d",
1680
tags, self.merge_marker(revision)))
1681
self.show_properties(revision.rev, indent+offset)
1691
1682
if self.show_ids or revision.revno is None:
1692
1683
to_file.write(indent + offset + 'revision-id:%s\n'
1693
% (revision.rev.revision_id.decode('utf-8'),))
1684
% (revision.rev.revision_id,))
1694
1685
if not revision.rev.message:
1695
1686
to_file.write(indent + offset + '(no message)\n')
1702
1693
# Use the standard status output to display changes
1703
1694
from breezy.delta import report_delta
1704
1695
report_delta(to_file, revision.delta,
1705
short_status=self.delta_format == 1,
1696
short_status=self.delta_format==1,
1706
1697
show_ids=self.show_ids, indent=indent + offset)
1707
1698
if revision.diff is not None:
1708
1699
self.show_diff(self.to_exact_file, revision.diff, ' ')
1726
1717
def truncate(self, str, max_len):
1727
1718
if max_len is None or len(str) <= max_len:
1729
return str[:max_len - 3] + '...'
1720
return str[:max_len-3] + '...'
1731
1722
def date_string(self, rev):
1732
1723
return format_date(rev.timestamp, rev.timezone or 0,
1742
1733
def log_revision(self, revision):
1743
1734
indent = ' ' * revision.merge_depth
1744
1735
self.to_file.write(self.log_string(revision.revno, revision.rev,
1745
self._max_chars, revision.tags, indent))
1736
self._max_chars, revision.tags, indent))
1746
1737
self.to_file.write('\n')
1748
1739
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1761
1752
# show revno only when is not None
1762
1753
out.append("%s:" % revno)
1763
1754
if max_chars is not None:
1764
out.append(self.truncate(
1765
self.short_author(rev), (max_chars + 3) // 4))
1755
out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1767
1757
out.append(self.short_author(rev))
1768
1758
out.append(self.date_string(rev))
1769
1759
if len(rev.parent_ids) > 1:
1770
1760
out.append('[merge]')
1772
tag_str = '{%s}' % (', '.join(sorted(tags)))
1762
tag_str = '{%s}' % (', '.join(tags))
1773
1763
out.append(tag_str)
1774
1764
out.append(rev.get_summary())
1775
1765
return self.truncate(prefix + " ".join(out).rstrip('\n'), max_chars)
1796
1786
if revision.delta is not None and revision.delta.has_changed():
1797
1787
for c in revision.delta.added + revision.delta.removed + revision.delta.modified:
1798
if c.path[0] is None:
1802
1789
to_file.write('\t* %s:\n' % (path,))
1803
for c in revision.delta.renamed + revision.delta.copied:
1790
for c in revision.delta.renamed:
1791
oldpath, newpath = c[:2]
1804
1792
# For renamed files, show both the old and the new path
1805
to_file.write('\t* %s:\n\t* %s:\n' % (c.path[0], c.path[1]))
1793
to_file.write('\t* %s:\n\t* %s:\n' % (oldpath, newpath))
1806
1794
to_file.write('\n')
1808
1796
if not revision.rev.message:
1862
1850
return log_formatter_registry.make_formatter(name, *args, **kwargs)
1863
1851
except KeyError:
1864
raise errors.BzrCommandError(
1865
gettext("unknown log formatter: %r") % name)
1852
raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
1868
1855
def author_list_all(rev):
1916
1903
for i in range(max(len(new_rh), len(old_rh))):
1917
1904
if (len(new_rh) <= i
1918
1905
or len(old_rh) <= i
1919
or new_rh[i] != old_rh[i]):
1906
or new_rh[i] != old_rh[i]):
1923
1910
if base_idx is None:
1924
1911
to_file.write('Nothing seems to have changed\n')
1926
# TODO: It might be nice to do something like show_log
1927
# and show the merged entries. But since this is the
1928
# removed revisions, it shouldn't be as important
1913
## TODO: It might be nice to do something like show_log
1914
## and show the merged entries. But since this is the
1915
## removed revisions, it shouldn't be as important
1929
1916
if base_idx < len(old_rh):
1930
to_file.write('*' * 60)
1917
to_file.write('*'*60)
1931
1918
to_file.write('\nRemoved Revisions:\n')
1932
1919
for i in range(base_idx, len(old_rh)):
1933
1920
rev = branch.repository.get_revision(old_rh[i])
1934
lr = LogRevision(rev, i + 1, 0, None)
1921
lr = LogRevision(rev, i+1, 0, None)
1935
1922
lf.log_revision(lr)
1936
to_file.write('*' * 60)
1923
to_file.write('*'*60)
1937
1924
to_file.write('\n\n')
1938
1925
if base_idx < len(new_rh):
1939
1926
to_file.write('Added Revisions:\n')
1940
1927
show_log(branch,
1943
1931
direction='forward',
1944
start_revision=base_idx + 1,
1932
start_revision=base_idx+1,
1945
1933
end_revision=len(new_rh),
2015
2003
log_format = log_formatter_registry.get_default(branch)
2016
2004
lf = log_format(show_ids=False, to_file=output, show_timezone='original')
2017
2005
if old_history != []:
2018
output.write('*' * 60)
2006
output.write('*'*60)
2019
2007
output.write('\nRemoved Revisions:\n')
2020
2008
show_flat_log(branch.repository, old_history, old_revno, lf)
2021
output.write('*' * 60)
2009
output.write('*'*60)
2022
2010
output.write('\n\n')
2023
2011
if new_history != []:
2024
2012
output.write('Added Revisions:\n')
2025
2013
start_revno = new_revno - len(new_history) + 1
2026
show_log(branch, lf, verbose=False, direction='forward',
2014
show_log(branch, lf, None, verbose=False, direction='forward',
2027
2015
start_revision=start_revno)
2035
2023
:param last_revno: The revno of the last revision_id in the history.
2036
2024
:param lf: The log formatter to use.
2026
start_revno = last_revno - len(history) + 1
2038
2027
revisions = repository.get_revisions(history)
2039
2028
for i, rev in enumerate(revisions):
2040
2029
lr = LogRevision(rev, i + last_revno, 0, None)
2041
2030
lf.log_revision(lr)
2044
def _get_info_for_log_files(revisionspec_list, file_list, exit_stack):
2033
def _get_info_for_log_files(revisionspec_list, file_list, add_cleanup):
2045
2034
"""Find file-ids and kinds given a list of files and a revision range.
2047
2036
We search for files at the end of the range. If not found there,
2051
2040
:param file_list: the list of paths given on the command line;
2052
2041
the first of these can be a branch location or a file path,
2053
2042
the remainder must be file paths
2054
:param exit_stack: When the branch returned is read locked,
2055
an unlock call will be queued to the exit stack.
2043
:param add_cleanup: When the branch returned is read locked,
2044
an unlock call will be queued to the cleanup.
2056
2045
:return: (branch, info_list, start_rev_info, end_rev_info) where
2057
2046
info_list is a list of (relative_path, file_id, kind) tuples where
2058
2047
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2061
2050
from breezy.builtins import _get_revision_range
2062
2051
tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2064
exit_stack.enter_context(b.lock_read())
2053
add_cleanup(b.lock_read().unlock)
2065
2054
# XXX: It's damn messy converting a list of paths to relative paths when
2066
2055
# those paths might be deleted ones, they might be on a case-insensitive
2067
2056
# filesystem and/or they might be in silly locations (like another branch).
2076
2065
relpaths = [path] + file_list[1:]
2078
2067
start_rev_info, end_rev_info = _get_revision_range(revisionspec_list, b,
2080
2069
if relpaths in ([], [u'']):
2081
2070
return b, [], start_rev_info, end_rev_info
2082
2071
if start_rev_info is None and end_rev_info is None:
2139
2128
def _get_kind_for_file_id(tree, path, file_id):
2140
2129
"""Return the kind of a file-id or None if it doesn't exist."""
2141
2130
if file_id is not None:
2142
return tree.kind(path)
2131
return tree.kind(path, file_id)
2147
2136
properties_handler_registry = registry.Registry()
2149
2138
# Use the properties handlers to print out bug information if available
2152
2139
def _bugs_properties_handler(revision):
2154
related_bug_urls = []
2155
for bug_url, status in revision.iter_bugs():
2156
if status == 'fixed':
2157
fixed_bug_urls.append(bug_url)
2158
elif status == 'related':
2159
related_bug_urls.append(bug_url)
2162
text = ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls))
2163
ret[text] = ' '.join(fixed_bug_urls)
2164
if related_bug_urls:
2165
text = ngettext('related bug', 'related bugs',
2166
len(related_bug_urls))
2167
ret[text] = ' '.join(related_bug_urls)
2140
if 'bugs' in revision.properties:
2141
bug_lines = revision.properties['bugs'].split('\n')
2142
bug_rows = [line.split(' ', 1) for line in bug_lines]
2143
fixed_bug_urls = [row[0] for row in bug_rows if
2144
len(row) > 1 and row[1] == 'fixed']
2147
return {ngettext('fixes bug', 'fixes bugs', len(fixed_bug_urls)):\
2148
' '.join(fixed_bug_urls)}
2171
2151
properties_handler_registry.register('bugs_properties_handler',
2172
2152
_bugs_properties_handler)