180
175
_show_log(branch, lf, specific_fileid, verbose, direction,
181
start_revision, end_revision, search, limit, show_diff)
176
start_revision, end_revision, search, limit)
183
178
if getattr(lf, 'end_log', None):
194
189
start_revision=None,
195
190
end_revision=None,
199
193
"""Worker function for show_log - see show_log."""
200
194
if not isinstance(lf, LogFormatter):
201
195
warn("not a LogFormatter instance: %r" % lf)
203
197
if specific_fileid:
204
198
trace.mutter('get log for file_id %r', specific_fileid)
205
levels_to_display = lf.get_levels()
206
generate_merge_revisions = levels_to_display != 1
207
allow_single_merge_revision = True
208
if not getattr(lf, 'supports_merge_revisions', False):
209
allow_single_merge_revision = getattr(lf,
210
'supports_single_merge_revision', False)
199
generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
200
allow_single_merge_revision = getattr(lf,
201
'supports_single_merge_revision', False)
211
202
view_revisions = calculate_view_revisions(branch, start_revision,
212
203
end_revision, direction,
220
211
rev_tag_dict = branch.tags.get_reverse_tag_dict()
222
213
generate_delta = verbose and getattr(lf, 'supports_delta', False)
223
generate_diff = show_diff and getattr(lf, 'supports_diff', False)
225
215
# now we just print all the revisions
226
repo = branch.repository
228
217
revision_iterator = make_log_rev_iterator(branch, view_revisions,
229
218
generate_delta, search)
230
219
for revs in revision_iterator:
231
220
for (rev_id, revno, merge_depth), rev, delta in revs:
232
# Note: 0 levels means show everything; merge_depth counts from 0
233
if levels_to_display != 0 and merge_depth >= levels_to_display:
236
diff = _format_diff(repo, rev, rev_id, specific_fileid)
239
221
lr = LogRevision(rev, revno, merge_depth, delta,
240
rev_tag_dict.get(rev_id), diff)
222
rev_tag_dict.get(rev_id))
241
223
lf.log_revision(lr)
248
def _format_diff(repo, rev, rev_id, specific_fileid):
249
if len(rev.parent_ids) == 0:
250
ancestor_id = _mod_revision.NULL_REVISION
252
ancestor_id = rev.parent_ids[0]
253
tree_1 = repo.revision_tree(ancestor_id)
254
tree_2 = repo.revision_tree(rev_id)
256
specific_files = [tree_2.id2path(specific_fileid)]
258
specific_files = None
260
diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
265
230
def calculate_view_revisions(branch, start_revision, end_revision, direction,
266
231
specific_fileid, generate_merge_revisions,
267
232
allow_single_merge_revision):
731
696
def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
732
tags=None, diff=None):
734
699
self.revno = revno
735
700
self.merge_depth = merge_depth
736
701
self.delta = delta
741
705
class LogFormatter(object):
758
722
merge revisions. If not, and if supports_single_merge_revisions is
759
723
also not True, then only mainline revisions will be passed to the
762
- preferred_levels is the number of levels this formatter defaults to.
763
The default value is zero meaning display all levels.
764
This value is only relevant if supports_merge_revisions is True.
766
725
- supports_single_merge_revision must be True if this log formatter
767
726
supports logging only a single merge revision. This flag is
768
727
only relevant if supports_merge_revisions is not True.
770
728
- supports_tags must be True if this log formatter supports tags.
771
729
Otherwise the tags attribute may not be populated.
773
- supports_diff must be True if this log formatter supports diffs.
774
Otherwise the diff attribute may not be populated.
776
731
Plugins can register functions to show custom revision properties using
777
732
the properties_handler_registry. The registered function
778
733
must respect the following interface description:
780
735
# code that returns a dict {'name':'value'} of the properties
785
739
def __init__(self, to_file, show_ids=False, show_timezone='original',
786
delta_format=None, levels=None):
787
"""Create a LogFormatter.
789
:param to_file: the file to output to
790
:param show_ids: if True, revision-ids are to be displayed
791
:param show_timezone: the timezone to use
792
:param delta_format: the level of delta information to display
793
or None to leave it u to the formatter to decide
794
:param levels: the number of levels to display; None or -1 to
795
let the log formatter decide.
797
741
self.to_file = to_file
798
742
self.show_ids = show_ids
799
743
self.show_timezone = show_timezone
801
745
# Ensures backward compatibility
802
746
delta_format = 2 # long format
803
747
self.delta_format = delta_format
806
def get_levels(self):
807
"""Get the number of levels to display or 0 for all."""
808
if getattr(self, 'supports_merge_revisions', False):
809
if self.levels is None or self.levels == -1:
810
return self.preferred_levels
815
def log_revision(self, revision):
818
:param revision: The LogRevision to be logged.
820
raise NotImplementedError('not implemented in abstract base')
749
# TODO: uncomment this block after show() has been removed.
750
# Until then defining log_revision would prevent _show_log calling show()
751
# in legacy formatters.
752
# def log_revision(self, revision):
755
# :param revision: The LogRevision to be logged.
757
# raise NotImplementedError('not implemented in abstract base')
822
759
def short_committer(self, rev):
823
760
name, address = config.parse_username(rev.committer)
840
777
for key, value in handler(revision).items():
841
778
self.to_file.write(indent + key + ': ' + value + '\n')
843
def show_diff(self, to_file, diff, indent):
844
for l in diff.rstrip().split('\n'):
845
to_file.write(indent + '%s\n' % (l,))
848
781
class LongLogFormatter(LogFormatter):
850
783
supports_merge_revisions = True
851
784
supports_delta = True
852
785
supports_tags = True
855
787
def log_revision(self, revision):
856
788
"""Log a revision, either merged or not."""
893
825
# We don't respect delta_format for compatibility
894
826
revision.delta.show(to_file, self.show_ids, indent=indent,
895
827
short_status=False)
896
if revision.diff is not None:
897
to_file.write(indent + 'diff:\n')
898
# Note: we explicitly don't indent the diff (relative to the
899
# revision information) so that the output can be fed to patch -p0
900
self.show_diff(to_file, revision.diff, indent)
903
830
class ShortLogFormatter(LogFormatter):
905
supports_merge_revisions = True
907
832
supports_delta = True
908
833
supports_tags = True
911
def __init__(self, *args, **kwargs):
912
super(ShortLogFormatter, self).__init__(*args, **kwargs)
913
self.revno_width_by_depth = {}
834
supports_single_merge_revision = True
915
836
def log_revision(self, revision):
916
# We need two indents: one per depth and one for the information
917
# relative to that indent. Most mainline revnos are 5 chars or
918
# less while dotted revnos are typically 11 chars or less. Once
919
# calculated, we need to remember the offset for a given depth
920
# as we might be starting from a dotted revno in the first column
921
# and we want subsequent mainline revisions to line up.
922
depth = revision.merge_depth
924
revno_width = self.revno_width_by_depth.get(depth)
925
if revno_width is None:
926
if revision.revno.find('.') == -1:
927
# mainline revno, e.g. 12345
930
# dotted revno, e.g. 12345.10.55
932
self.revno_width_by_depth[depth] = revno_width
933
offset = ' ' * (revno_width + 1)
935
837
to_file = self.to_file
937
839
if len(revision.rev.parent_ids) > 1:
940
842
if revision.tags:
941
843
tags = ' {%s}' % (', '.join(revision.tags))
942
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
943
revision.revno, self.short_author(revision.rev),
845
to_file.write("%5s %s\t%s%s%s\n" % (revision.revno,
846
self.short_author(revision.rev),
944
847
format_date(revision.rev.timestamp,
945
848
revision.rev.timezone or 0,
946
849
self.show_timezone, date_fmt="%Y-%m-%d",
947
850
show_offset=False),
949
852
if self.show_ids:
950
to_file.write(indent + offset + 'revision-id:%s\n'
853
to_file.write(' revision-id:%s\n'
951
854
% (revision.rev.revision_id,))
952
855
if not revision.rev.message:
953
to_file.write(indent + offset + '(no message)\n')
856
to_file.write(' (no message)\n')
955
858
message = revision.rev.message.rstrip('\r\n')
956
859
for l in message.split('\n'):
957
to_file.write(indent + offset + '%s\n' % (l,))
860
to_file.write(' %s\n' % (l,))
959
862
if revision.delta is not None:
960
revision.delta.show(to_file, self.show_ids, indent=indent + offset,
863
revision.delta.show(to_file, self.show_ids,
961
864
short_status=self.delta_format==1)
962
if revision.diff is not None:
963
self.show_diff(to_file, revision.diff, ' ')
964
865
to_file.write('\n')
967
868
class LineLogFormatter(LogFormatter):
969
supports_merge_revisions = True
971
870
supports_tags = True
871
supports_single_merge_revision = True
973
873
def __init__(self, *args, **kwargs):
974
874
super(LineLogFormatter, self).__init__(*args, **kwargs)
991
891
return rev.message
993
893
def log_revision(self, revision):
994
indent = ' ' * revision.merge_depth
995
894
self.to_file.write(self.log_string(revision.revno, revision.rev,
996
self._max_chars, revision.tags, indent))
895
self._max_chars, revision.tags))
997
896
self.to_file.write('\n')
999
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
898
def log_string(self, revno, rev, max_chars, tags=None):
1000
899
"""Format log info into one string. Truncate tail of string
1001
900
:param revno: revision number or None.
1002
901
Revision numbers counts from 1.
1003
902
:param rev: revision object
1004
903
:param max_chars: maximum length of resulting string
1005
904
:param tags: list of tags or None
1006
:param prefix: string to prefix each line
1007
905
:return: formatted truncated string
1016
914
tag_str = '{%s}' % (', '.join(tags))
1017
915
out.append(tag_str)
1018
916
out.append(rev.get_summary())
1019
return self.truncate(prefix + " ".join(out).rstrip('\n'), max_chars)
917
return self.truncate(" ".join(out).rstrip('\n'), max_chars)
1022
920
def line_log(rev, max_chars):
1222
1120
lf.log_revision(lr)
1225
def _get_fileid_to_log(revision, tree, b, fp):
1226
"""Find the file-id to log for a file path in a revision range.
1228
:param revision: the revision range as parsed on the command line
1229
:param tree: the working tree, if any
1230
:param b: the branch
1231
:param fp: file path
1233
if revision is None:
1235
tree = b.basis_tree()
1236
file_id = tree.path2id(fp)
1238
# go back to when time began
1240
rev1 = b.get_rev_id(1)
1241
except errors.NoSuchRevision:
1245
tree = b.repository.revision_tree(rev1)
1246
file_id = tree.path2id(fp)
1248
elif len(revision) == 1:
1249
# One revision given - file must exist in it
1250
tree = revision[0].as_tree(b)
1251
file_id = tree.path2id(fp)
1253
elif len(revision) == 2:
1254
# Revision range given. Get the file-id from the end tree.
1255
# If that fails, try the start tree.
1256
rev_id = revision[1].as_revision_id(b)
1258
tree = b.basis_tree()
1260
tree = revision[1].as_tree(b)
1261
file_id = tree.path2id(fp)
1263
rev_id = revision[0].as_revision_id(b)
1265
rev1 = b.get_rev_id(1)
1266
tree = b.repository.revision_tree(rev1)
1268
tree = revision[0].as_tree(b)
1269
file_id = tree.path2id(fp)
1271
raise errors.BzrCommandError(
1272
'bzr log --revision takes one or two values.')
1276
1123
properties_handler_registry = registry.Registry()
1277
1124
properties_handler_registry.register_lazy("foreign",
1278
1125
"bzrlib.foreign",