209
214
Logger(branch, rqst).show(lf)
212
# Note: This needs to be kept in sync with the defaults in
217
# Note: This needs to be kept this in sync with the defaults in
213
218
# make_log_request_dict() below
214
219
_DEFAULT_REQUEST_PARAMS = {
215
220
'direction': 'reverse',
217
222
'generate_tags': True,
218
'exclude_common_ancestry': False,
219
223
'_match_using_deltas': True,
223
227
def make_log_request_dict(direction='reverse', specific_fileids=None,
224
start_revision=None, end_revision=None, limit=None,
225
message_search=None, levels=None, generate_tags=True,
227
diff_type=None, _match_using_deltas=True,
228
exclude_common_ancestry=False, match=None,
229
signature=False, omit_merges=False,
228
start_revision=None, end_revision=None, limit=None,
229
message_search=None, levels=1, generate_tags=True, delta_type=None,
230
diff_type=None, _match_using_deltas=True):
231
231
"""Convenience function for making a logging request dictionary.
233
233
Using this function may make code slightly safer by ensuring
272
271
algorithm used for matching specific_fileids. This parameter
273
272
may be removed in the future so bzrlib client code should NOT
276
:param exclude_common_ancestry: Whether -rX..Y should be interpreted as a
277
range operator or as a graph difference.
279
:param signature: show digital signature information
281
:param match: Dictionary of list of search strings to use when filtering
282
revisions. Keys can be 'message', 'author', 'committer', 'bugs' or
283
the empty string to match any of the preceding properties.
285
:param omit_merges: If True, commits with more than one parent are
289
# Take care of old style message_search parameter
292
if 'message' in match:
293
match['message'].append(message_search)
295
match['message'] = [message_search]
297
match={ 'message': [message_search] }
299
276
'direction': direction,
300
277
'specific_fileids': specific_fileids,
301
278
'start_revision': start_revision,
302
279
'end_revision': end_revision,
281
'message_search': message_search,
304
282
'levels': levels,
305
283
'generate_tags': generate_tags,
306
284
'delta_type': delta_type,
307
285
'diff_type': diff_type,
308
'exclude_common_ancestry': exclude_common_ancestry,
309
'signature': signature,
311
'omit_merges': omit_merges,
312
286
# Add 'private' attributes for features that may be deprecated
313
287
'_match_using_deltas': _match_using_deltas,
317
291
def _apply_log_request_defaults(rqst):
318
292
"""Apply default values to a request dictionary."""
319
result = _DEFAULT_REQUEST_PARAMS.copy()
293
result = _DEFAULT_REQUEST_PARAMS
321
295
result.update(rqst)
325
def format_signature_validity(rev_id, repo):
326
"""get the signature validity
328
:param rev_id: revision id to validate
329
:param repo: repository of revision
330
:return: human readable string to print to log
332
from bzrlib import gpg
334
gpg_strategy = gpg.GPGStrategy(None)
335
result = repo.verify_revision_signature(rev_id, gpg_strategy)
336
if result[0] == gpg.SIGNATURE_VALID:
337
return u"valid signature from {0}".format(result[1])
338
if result[0] == gpg.SIGNATURE_KEY_MISSING:
339
return "unknown key {0}".format(result[1])
340
if result[0] == gpg.SIGNATURE_NOT_VALID:
341
return "invalid signature!"
342
if result[0] == gpg.SIGNATURE_NOT_SIGNED:
343
return "no signature"
346
299
class LogGenerator(object):
347
300
"""A generator of log revisions."""
393
346
# Tweak the LogRequest based on what the LogFormatter can handle.
394
347
# (There's no point generating stuff if the formatter can't display it.)
396
if rqst['levels'] is None or lf.get_levels() > rqst['levels']:
397
# user didn't specify levels, use whatever the LF can handle:
398
rqst['levels'] = lf.get_levels()
349
rqst['levels'] = lf.get_levels()
400
350
if not getattr(lf, 'supports_tags', False):
401
351
rqst['generate_tags'] = False
402
352
if not getattr(lf, 'supports_delta', False):
403
353
rqst['delta_type'] = None
404
354
if not getattr(lf, 'supports_diff', False):
405
355
rqst['diff_type'] = None
406
if not getattr(lf, 'supports_signatures', False):
407
rqst['signature'] = False
409
357
# Find and print the interesting revisions
410
358
generator = self._generator_factory(self.branch, rqst)
517
455
generate_merge_revisions = rqst.get('levels') != 1
518
456
delayed_graph_generation = not rqst.get('specific_fileids') and (
519
457
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
520
view_revisions = _calc_view_revisions(
521
self.branch, self.start_rev_id, self.end_rev_id,
522
rqst.get('direction'),
523
generate_merge_revisions=generate_merge_revisions,
524
delayed_graph_generation=delayed_graph_generation,
525
exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
458
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
459
self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
460
delayed_graph_generation=delayed_graph_generation)
527
462
# Apply the other filters
528
463
return make_log_rev_iterator(self.branch, view_revisions,
529
rqst.get('delta_type'), rqst.get('match'),
464
rqst.get('delta_type'), rqst.get('message_search'),
530
465
file_ids=rqst.get('specific_fileids'),
531
466
direction=rqst.get('direction'))
535
470
# Note that we always generate the merge revisions because
536
471
# filter_revisions_touching_file_id() requires them ...
538
view_revisions = _calc_view_revisions(
539
self.branch, self.start_rev_id, self.end_rev_id,
540
rqst.get('direction'), generate_merge_revisions=True,
541
exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
473
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
474
self.end_rev_id, rqst.get('direction'), True)
542
475
if not isinstance(view_revisions, list):
543
476
view_revisions = list(view_revisions)
544
477
view_revisions = _filter_revisions_touching_file_id(self.branch,
545
478
rqst.get('specific_fileids')[0], view_revisions,
546
479
include_merges=rqst.get('levels') != 1)
547
480
return make_log_rev_iterator(self.branch, view_revisions,
548
rqst.get('delta_type'), rqst.get('match'))
481
rqst.get('delta_type'), rqst.get('message_search'))
551
484
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
552
generate_merge_revisions,
553
delayed_graph_generation=False,
554
exclude_common_ancestry=False,
485
generate_merge_revisions, delayed_graph_generation=False):
556
486
"""Calculate the revisions to view.
558
488
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
559
489
a list of the same tuples.
561
if (exclude_common_ancestry and start_rev_id == end_rev_id):
562
raise errors.BzrCommandError(gettext(
563
'--exclude-common-ancestry requires two different revisions'))
564
if direction not in ('reverse', 'forward'):
565
raise ValueError(gettext('invalid direction %r') % direction)
566
491
br_revno, br_rev_id = branch.last_revision_info()
567
492
if br_revno == 0:
570
if (end_rev_id and start_rev_id == end_rev_id
571
and (not generate_merge_revisions
572
or not _has_merges(branch, end_rev_id))):
573
# If a single revision is requested, check we can handle it
574
return _generate_one_revision(branch, end_rev_id, br_rev_id,
495
# If a single revision is requested, check we can handle it
496
generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
497
(not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
498
if generate_single_revision:
499
return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
501
# If we only want to see linear revisions, we can iterate ...
576
502
if not generate_merge_revisions:
578
# If we only want to see linear revisions, we can iterate ...
579
iter_revs = _linear_view_revisions(
580
branch, start_rev_id, end_rev_id,
581
exclude_common_ancestry=exclude_common_ancestry)
582
# If a start limit was given and it's not obviously an
583
# ancestor of the end limit, check it before outputting anything
584
if (direction == 'forward'
585
or (start_rev_id and not _is_obvious_ancestor(
586
branch, start_rev_id, end_rev_id))):
587
iter_revs = list(iter_revs)
588
if direction == 'forward':
589
iter_revs = reversed(iter_revs)
591
except _StartNotLinearAncestor:
592
# Switch to the slower implementation that may be able to find a
593
# non-obvious ancestor out of the left-hand history.
595
iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
596
direction, delayed_graph_generation,
597
exclude_common_ancestry)
598
if direction == 'forward':
599
iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
503
return _generate_flat_revisions(branch, start_rev_id, end_rev_id,
506
return _generate_all_revisions(branch, start_rev_id, end_rev_id,
507
direction, delayed_graph_generation)
603
510
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
606
513
return [(br_rev_id, br_revno, 0)]
608
revno_str = _compute_revno_str(branch, rev_id)
515
revno = branch.revision_id_to_dotted_revno(rev_id)
516
revno_str = '.'.join(str(n) for n in revno)
609
517
return [(rev_id, revno_str, 0)]
520
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction):
521
result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
522
# If a start limit was given and it's not obviously an
523
# ancestor of the end limit, check it before outputting anything
524
if direction == 'forward' or (start_rev_id
525
and not _is_obvious_ancestor(branch, start_rev_id, end_rev_id)):
527
result = list(result)
528
except _StartNotLinearAncestor:
529
raise errors.BzrCommandError('Start revision not found in'
530
' left-hand history of end revision.')
531
if direction == 'forward':
532
result = reversed(result)
612
536
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
613
delayed_graph_generation,
614
exclude_common_ancestry=False):
537
delayed_graph_generation):
615
538
# On large trees, generating the merge graph can take 30-60 seconds
616
539
# so we delay doing it until a merge is detected, incrementally
617
540
# returning initial (non-merge) revisions while we can.
646
569
initial_revisions.append((rev_id, revno, depth))
648
571
# No merged revisions found
649
return initial_revisions
572
if direction == 'reverse':
573
return initial_revisions
574
elif direction == 'forward':
575
return reversed(initial_revisions)
577
raise ValueError('invalid direction %r' % direction)
650
578
except _StartNotLinearAncestor:
651
579
# A merge was never detected so the lower revision limit can't
652
580
# be nested down somewhere
653
raise errors.BzrCommandError(gettext('Start revision not found in'
654
' history of end revision.'))
581
raise errors.BzrCommandError('Start revision not found in'
582
' history of end revision.')
656
584
# We exit the loop above because we encounter a revision with merges, from
657
585
# this revision, we need to switch to _graph_view_revisions.
663
591
# indented at the end seems slightly nicer in that case.
664
592
view_revisions = chain(iter(initial_revisions),
665
593
_graph_view_revisions(branch, start_rev_id, end_rev_id,
666
rebase_initial_depths=(direction == 'reverse'),
667
exclude_common_ancestry=exclude_common_ancestry))
668
return view_revisions
594
rebase_initial_depths=(direction == 'reverse')))
595
if direction == 'reverse':
596
return view_revisions
597
elif direction == 'forward':
598
# Forward means oldest first, adjusting for depth.
599
view_revisions = reverse_by_depth(list(view_revisions))
600
return _rebase_merge_depth(view_revisions)
602
raise ValueError('invalid direction %r' % direction)
671
605
def _has_merges(branch, rev_id):
674
608
return len(parents) > 1
677
def _compute_revno_str(branch, rev_id):
678
"""Compute the revno string from a rev_id.
680
:return: The revno string, or None if the revision is not in the supplied
684
revno = branch.revision_id_to_dotted_revno(rev_id)
685
except errors.NoSuchRevision:
686
# The revision must be outside of this branch
689
return '.'.join(str(n) for n in revno)
692
611
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
693
612
"""Is start_rev_id an obvious ancestor of end_rev_id?"""
694
613
if start_rev_id and end_rev_id:
696
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
697
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
698
except errors.NoSuchRevision:
699
# one or both is not in the branch; not obvious
614
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
615
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
701
616
if len(start_dotted) == 1 and len(end_dotted) == 1:
702
617
# both on mainline
703
618
return start_dotted[0] <= end_dotted[0]
716
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
717
exclude_common_ancestry=False):
631
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
718
632
"""Calculate a sequence of revisions to view, newest to oldest.
720
634
:param start_rev_id: the lower revision-id
721
635
:param end_rev_id: the upper revision-id
722
:param exclude_common_ancestry: Whether the start_rev_id should be part of
723
the iterated revisions.
724
636
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
725
637
:raises _StartNotLinearAncestor: if a start_rev_id is specified but
726
is not found walking the left-hand history
638
is not found walking the left-hand history
728
640
br_revno, br_rev_id = branch.last_revision_info()
729
641
repo = branch.repository
730
graph = repo.get_graph()
731
642
if start_rev_id is None and end_rev_id is None:
732
643
cur_revno = br_revno
733
for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
734
(_mod_revision.NULL_REVISION,)):
644
for revision_id in repo.iter_reverse_revision_history(br_rev_id):
735
645
yield revision_id, str(cur_revno), 0
738
648
if end_rev_id is None:
739
649
end_rev_id = br_rev_id
740
650
found_start = start_rev_id is None
741
for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
742
(_mod_revision.NULL_REVISION,)):
743
revno_str = _compute_revno_str(branch, revision_id)
651
for revision_id in repo.iter_reverse_revision_history(end_rev_id):
652
revno = branch.revision_id_to_dotted_revno(revision_id)
653
revno_str = '.'.join(str(n) for n in revno)
744
654
if not found_start and revision_id == start_rev_id:
745
if not exclude_common_ancestry:
746
yield revision_id, revno_str, 0
655
yield revision_id, revno_str, 0
747
656
found_start = True
797
701
yield rev_id, '.'.join(map(str, revno)), merge_depth
704
@deprecated_function(deprecated_in((2, 2, 0)))
705
def calculate_view_revisions(branch, start_revision, end_revision, direction,
706
specific_fileid, generate_merge_revisions):
707
"""Calculate the revisions to view.
709
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
710
a list of the same tuples.
712
start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
714
view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
715
direction, generate_merge_revisions or specific_fileid))
717
view_revisions = _filter_revisions_touching_file_id(branch,
718
specific_fileid, view_revisions,
719
include_merges=generate_merge_revisions)
720
return _rebase_merge_depth(view_revisions)
800
723
def _rebase_merge_depth(view_revisions):
801
724
"""Adjust depths upwards so the top level is 0."""
802
725
# If either the first or last revision have a merge_depth of 0, we're done
846
769
return log_rev_iterator
849
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
772
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
850
773
"""Create a filtered iterator of log_rev_iterator matching on a regex.
852
775
:param branch: The branch being logged.
853
776
:param generate_delta: Whether to generate a delta for each revision.
854
:param match: A dictionary with properties as keys and lists of strings
855
as values. To match, a revision may match any of the supplied strings
856
within a single property but must match at least one string for each
777
:param search: A user text search string.
858
778
:param log_rev_iterator: An input iterator containing all revisions that
859
779
could be displayed, in lists.
860
780
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
864
784
return log_rev_iterator
865
searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
866
for (k,v) in match.iteritems()]
867
return _filter_re(searchRE, log_rev_iterator)
870
def _filter_re(searchRE, log_rev_iterator):
785
searchRE = re_compile_checked(search, re.IGNORECASE,
786
'log message filter')
787
return _filter_message_re(searchRE, log_rev_iterator)
790
def _filter_message_re(searchRE, log_rev_iterator):
871
791
for revs in log_rev_iterator:
872
new_revs = [rev for rev in revs if _match_filter(searchRE, rev[1])]
876
def _match_filter(searchRE, rev):
878
'message': (rev.message,),
879
'committer': (rev.committer,),
880
'author': (rev.get_apparent_authors()),
881
'bugs': list(rev.iter_bugs())
883
strings[''] = [item for inner_list in strings.itervalues()
884
for item in inner_list]
885
for (k,v) in searchRE:
886
if k in strings and not _match_any_filter(strings[k], v):
890
def _match_any_filter(strings, res):
891
return any([filter(None, map(re.search, strings)) for re in res])
793
for (rev_id, revno, merge_depth), rev, delta in revs:
794
if searchRE.search(rev.message):
795
new_revs.append(((rev_id, revno, merge_depth), rev, delta))
893
799
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
894
800
fileids=None, direction='reverse'):
1127
1034
if ((start_rev_id == _mod_revision.NULL_REVISION)
1128
1035
or (end_rev_id == _mod_revision.NULL_REVISION)):
1129
raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1036
raise errors.BzrCommandError('Logging revision 0 is invalid.')
1130
1037
if start_revno > end_revno:
1131
raise errors.BzrCommandError(gettext("Start revision must be older "
1132
"than the end revision."))
1038
raise errors.BzrCommandError("Start revision must be older than "
1039
"the end revision.")
1134
1041
if end_revno < start_revno:
1135
1042
return None, None, None, None
1136
1043
cur_revno = branch_revno
1138
1045
mainline_revs = []
1139
graph = branch.repository.get_graph()
1140
for revision_id in graph.iter_lefthand_ancestry(
1141
branch_last_revision, (_mod_revision.NULL_REVISION,)):
1046
for revision_id in branch.repository.iter_reverse_revision_history(
1047
branch_last_revision):
1142
1048
if cur_revno < start_revno:
1143
1049
# We have gone far enough, but we always add 1 more revision
1144
1050
rev_nos[revision_id] = cur_revno
1158
1064
return mainline_revs, rev_nos, start_rev_id, end_rev_id
1067
@deprecated_function(deprecated_in((2, 2, 0)))
1068
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
1069
"""Filter view_revisions based on revision ranges.
1071
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
1072
tuples to be filtered.
1074
:param start_rev_id: If not NONE specifies the first revision to be logged.
1075
If NONE then all revisions up to the end_rev_id are logged.
1077
:param end_rev_id: If not NONE specifies the last revision to be logged.
1078
If NONE then all revisions up to the end of the log are logged.
1080
:return: The filtered view_revisions.
1082
if start_rev_id or end_rev_id:
1083
revision_ids = [r for r, n, d in view_revisions]
1085
start_index = revision_ids.index(start_rev_id)
1088
if start_rev_id == end_rev_id:
1089
end_index = start_index
1092
end_index = revision_ids.index(end_rev_id)
1094
end_index = len(view_revisions) - 1
1095
# To include the revisions merged into the last revision,
1096
# extend end_rev_id down to, but not including, the next rev
1097
# with the same or lesser merge_depth
1098
end_merge_depth = view_revisions[end_index][2]
1100
for index in xrange(end_index+1, len(view_revisions)+1):
1101
if view_revisions[index][2] <= end_merge_depth:
1102
end_index = index - 1
1105
# if the search falls off the end then log to the end as well
1106
end_index = len(view_revisions) - 1
1107
view_revisions = view_revisions[start_index:end_index+1]
1108
return view_revisions
1161
1111
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1162
1112
include_merges=True):
1163
1113
r"""Return the list of revision ids which touch a given file id.
1193
@deprecated_function(deprecated_in((2, 2, 0)))
1194
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
1195
include_merges=True):
1196
"""Produce an iterator of revisions to show
1197
:return: an iterator of (revision_id, revno, merge_depth)
1198
(if there is no revno for a revision, None is supplied)
1200
if not include_merges:
1201
revision_ids = mainline_revs[1:]
1202
if direction == 'reverse':
1203
revision_ids.reverse()
1204
for revision_id in revision_ids:
1205
yield revision_id, str(rev_nos[revision_id]), 0
1207
graph = branch.repository.get_graph()
1208
# This asks for all mainline revisions, which means we only have to spider
1209
# sideways, rather than depth history. That said, its still size-of-history
1210
# and should be addressed.
1211
# mainline_revisions always includes an extra revision at the beginning, so
1213
parent_map = dict(((key, value) for key, value in
1214
graph.iter_ancestry(mainline_revs[1:]) if value is not None))
1215
# filter out ghosts; merge_sort errors on ghosts.
1216
rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
1217
merge_sorted_revisions = tsort.merge_sort(
1221
generate_revno=True)
1223
if direction == 'forward':
1224
# forward means oldest first.
1225
merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
1226
elif direction != 'reverse':
1227
raise ValueError('invalid direction %r' % direction)
1229
for (sequence, rev_id, merge_depth, revno, end_of_merge
1230
) in merge_sorted_revisions:
1231
yield rev_id, '.'.join(map(str, revno)), merge_depth
1245
1234
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1246
1235
"""Reverse revisions by depth.
1307
1292
to indicate which LogRevision attributes it supports:
1309
1294
- supports_delta must be True if this log formatter supports delta.
1310
Otherwise the delta attribute may not be populated. The 'delta_format'
1311
attribute describes whether the 'short_status' format (1) or the long
1312
one (2) should be used.
1295
Otherwise the delta attribute may not be populated. The 'delta_format'
1296
attribute describes whether the 'short_status' format (1) or the long
1297
one (2) should be used.
1314
1299
- supports_merge_revisions must be True if this log formatter supports
1315
merge revisions. If not, then only mainline revisions will be passed
1300
merge revisions. If not, then only mainline revisions will be passed
1318
1303
- preferred_levels is the number of levels this formatter defaults to.
1319
The default value is zero meaning display all levels.
1320
This value is only relevant if supports_merge_revisions is True.
1304
The default value is zero meaning display all levels.
1305
This value is only relevant if supports_merge_revisions is True.
1322
1307
- supports_tags must be True if this log formatter supports tags.
1323
Otherwise the tags attribute may not be populated.
1308
Otherwise the tags attribute may not be populated.
1325
1310
- supports_diff must be True if this log formatter supports diffs.
1326
Otherwise the diff attribute may not be populated.
1328
- supports_signatures must be True if this log formatter supports GPG
1311
Otherwise the diff attribute may not be populated.
1331
1313
Plugins can register functions to show custom revision properties using
1332
1314
the properties_handler_registry. The registered function
1333
must respect the following interface description::
1315
must respect the following interface description:
1335
1316
def my_show_properties(properties_dict):
1336
1317
# code that returns a dict {'name':'value'} of the properties
1416
1394
def short_author(self, rev):
1417
return self.authors(rev, 'first', short=True, sep=', ')
1419
def authors(self, rev, who, short=False, sep=None):
1420
"""Generate list of authors, taking --authors option into account.
1422
The caller has to specify the name of a author list handler,
1423
as provided by the author list registry, using the ``who``
1424
argument. That name only sets a default, though: when the
1425
user selected a different author list generation using the
1426
``--authors`` command line switch, as represented by the
1427
``author_list_handler`` constructor argument, that value takes
1430
:param rev: The revision for which to generate the list of authors.
1431
:param who: Name of the default handler.
1432
:param short: Whether to shorten names to either name or address.
1433
:param sep: What separator to use for automatic concatenation.
1435
if self._author_list_handler is not None:
1436
# The user did specify --authors, which overrides the default
1437
author_list_handler = self._author_list_handler
1439
# The user didn't specify --authors, so we use the caller's default
1440
author_list_handler = author_list_registry.get(who)
1441
names = author_list_handler(rev)
1443
for i in range(len(names)):
1444
name, address = config.parse_username(names[i])
1450
names = sep.join(names)
1395
name, address = config.parse_username(rev.get_apparent_authors()[0])
1453
1400
def merge_marker(self, revision):
1454
1401
"""Get the merge marker to include in the output or '' if none."""
1550
1496
self.merge_marker(revision)))
1551
1497
if revision.tags:
1552
1498
lines.append('tags: %s' % (', '.join(revision.tags)))
1553
if self.show_ids or revision.revno is None:
1554
1500
lines.append('revision-id: %s' % (revision.rev.revision_id,))
1556
1501
for parent_id in revision.rev.parent_ids:
1557
1502
lines.append('parent: %s' % (parent_id,))
1558
1503
lines.extend(self.custom_properties(revision.rev))
1560
1505
committer = revision.rev.committer
1561
authors = self.authors(revision.rev, 'all')
1506
authors = revision.rev.get_apparent_authors()
1562
1507
if authors != [committer]:
1563
1508
lines.append('author: %s' % (", ".join(authors),))
1564
1509
lines.append('committer: %s' % (committer,))
1638
1579
if revision.tags:
1639
1580
tags = ' {%s}' % (', '.join(revision.tags))
1640
1581
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1641
revision.revno or "", self.short_author(revision.rev),
1582
revision.revno, self.short_author(revision.rev),
1642
1583
format_date(revision.rev.timestamp,
1643
1584
revision.rev.timezone or 0,
1644
1585
self.show_timezone, date_fmt="%Y-%m-%d",
1645
1586
show_offset=False),
1646
1587
tags, self.merge_marker(revision)))
1647
1588
self.show_properties(revision.rev, indent+offset)
1648
if self.show_ids or revision.revno is None:
1649
1590
to_file.write(indent + offset + 'revision-id:%s\n'
1650
1591
% (revision.rev.revision_id,))
1651
1592
if not revision.rev.message:
1705
1643
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1706
1644
"""Format log info into one string. Truncate tail of string
1708
:param revno: revision number or None.
1709
Revision numbers counts from 1.
1710
:param rev: revision object
1711
:param max_chars: maximum length of resulting string
1712
:param tags: list of tags or None
1713
:param prefix: string to prefix each line
1714
:return: formatted truncated string
1645
:param revno: revision number or None.
1646
Revision numbers counts from 1.
1647
:param rev: revision object
1648
:param max_chars: maximum length of resulting string
1649
:param tags: list of tags or None
1650
:param prefix: string to prefix each line
1651
:return: formatted truncated string
1718
1655
# show revno only when is not None
1719
1656
out.append("%s:" % revno)
1720
if max_chars is not None:
1721
out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1723
out.append(self.short_author(rev))
1657
out.append(self.truncate(self.short_author(rev), 20))
1724
1658
out.append(self.date_string(rev))
1725
1659
if len(rev.parent_ids) > 1:
1726
1660
out.append('[merge]')
1785
1718
return self.get(name)(*args, **kwargs)
1787
1720
def get_default(self, branch):
1788
c = branch.get_config_stack()
1789
return self.get(c.get('log_format'))
1721
return self.get(branch.get_config().log_format())
1792
1724
log_formatter_registry = LogFormatterRegistry()
1795
1727
log_formatter_registry.register('short', ShortLogFormatter,
1796
'Moderately short log format.')
1728
'Moderately short log format')
1797
1729
log_formatter_registry.register('long', LongLogFormatter,
1798
'Detailed log format.')
1730
'Detailed log format')
1799
1731
log_formatter_registry.register('line', LineLogFormatter,
1800
'Log format with one line per revision.')
1732
'Log format with one line per revision')
1801
1733
log_formatter_registry.register('gnu-changelog', GnuChangelogLogFormatter,
1802
'Format used by GNU ChangeLog files.')
1734
'Format used by GNU ChangeLog files')
1805
1737
def register_formatter(name, formatter):
1816
1748
return log_formatter_registry.make_formatter(name, *args, **kwargs)
1817
1749
except KeyError:
1818
raise errors.BzrCommandError(gettext("unknown log formatter: %r") % name)
1821
def author_list_all(rev):
1822
return rev.get_apparent_authors()[:]
1825
def author_list_first(rev):
1826
lst = rev.get_apparent_authors()
1833
def author_list_committer(rev):
1834
return [rev.committer]
1837
author_list_registry = registry.Registry()
1839
author_list_registry.register('all', author_list_all,
1842
author_list_registry.register('first', author_list_first,
1845
author_list_registry.register('committer', author_list_committer,
1750
raise errors.BzrCommandError("unknown log formatter: %r" % name)
1753
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1754
# deprecated; for compatibility
1755
lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1756
lf.show(revno, rev, delta)
1849
1759
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
2007
1916
:param file_list: the list of paths given on the command line;
2008
1917
the first of these can be a branch location or a file path,
2009
1918
the remainder must be file paths
2010
:param add_cleanup: When the branch returned is read locked,
2011
an unlock call will be queued to the cleanup.
2012
1919
:return: (branch, info_list, start_rev_info, end_rev_info) where
2013
1920
info_list is a list of (relative_path, file_id, kind) tuples where
2014
1921
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2015
1922
branch will be read-locked.
2017
from bzrlib.builtins import _get_revision_range
2018
tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2020
add_cleanup(b.lock_read().unlock)
1924
from builtins import _get_revision_range, safe_relpath_files
1925
tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2021
1927
# XXX: It's damn messy converting a list of paths to relative paths when
2022
1928
# those paths might be deleted ones, they might be on a case-insensitive
2023
1929
# filesystem and/or they might be in silly locations (like another branch).