215
221
'direction': 'reverse',
217
223
'generate_tags': True,
224
'exclude_common_ancestry': False,
218
225
'_match_using_deltas': True,
222
229
def make_log_request_dict(direction='reverse', specific_fileids=None,
223
start_revision=None, end_revision=None, limit=None,
224
message_search=None, levels=1, generate_tags=True, delta_type=None,
225
diff_type=None, _match_using_deltas=True):
230
start_revision=None, end_revision=None, limit=None,
231
message_search=None, levels=1, generate_tags=True,
233
diff_type=None, _match_using_deltas=True,
234
exclude_common_ancestry=False,
226
237
"""Convenience function for making a logging request dictionary.
228
239
Using this function may make code slightly safer by ensuring
286
304
def _apply_log_request_defaults(rqst):
287
305
"""Apply default values to a request dictionary."""
288
result = _DEFAULT_REQUEST_PARAMS
306
result = _DEFAULT_REQUEST_PARAMS.copy()
290
308
result.update(rqst)
312
def format_signature_validity(rev_id, repo):
313
"""get the signature validity
315
:param rev_id: revision id to validate
316
:param repo: repository of revision
317
:return: human readable string to print to log
319
from bzrlib import gpg
321
gpg_strategy = gpg.GPGStrategy(None)
322
result = repo.verify_revision(rev_id, gpg_strategy)
323
if result[0] == gpg.SIGNATURE_VALID:
324
return "valid signature from {0}".format(result[1])
325
if result[0] == gpg.SIGNATURE_KEY_MISSING:
326
return "unknown key {0}".format(result[1])
327
if result[0] == gpg.SIGNATURE_NOT_VALID:
328
return "invalid signature!"
329
if result[0] == gpg.SIGNATURE_NOT_SIGNED:
330
return "no signature"
294
333
class LogGenerator(object):
295
334
"""A generator of log revisions."""
384
425
:return: An iterator yielding LogRevision objects.
428
levels = rqst.get('levels')
429
limit = rqst.get('limit')
430
diff_type = rqst.get('diff_type')
431
show_signature = rqst.get('signature')
388
433
revision_iterator = self._create_log_revision_iterator()
389
434
for revs in revision_iterator:
390
435
for (rev_id, revno, merge_depth), rev, delta in revs:
391
436
# 0 levels means show everything; merge_depth counts from 0
392
levels = rqst.get('levels')
393
437
if levels != 0 and merge_depth >= levels:
395
diff = self._format_diff(rev, rev_id)
439
if diff_type is None:
442
diff = self._format_diff(rev, rev_id, diff_type)
444
signature = format_signature_validity(rev_id,
445
self.branch.repository)
396
448
yield LogRevision(rev, revno, merge_depth, delta,
397
self.rev_tag_dict.get(rev_id), diff)
398
limit = rqst.get('limit')
449
self.rev_tag_dict.get(rev_id), diff, signature)
401
452
if log_count >= limit:
404
def _format_diff(self, rev, rev_id):
405
diff_type = self.rqst.get('diff_type')
406
if diff_type is None:
455
def _format_diff(self, rev, rev_id, diff_type):
408
456
repo = self.branch.repository
409
457
if len(rev.parent_ids) == 0:
410
458
ancestor_id = _mod_revision.NULL_REVISION
449
498
generate_merge_revisions = rqst.get('levels') != 1
450
499
delayed_graph_generation = not rqst.get('specific_fileids') and (
451
500
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
452
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
453
self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
454
delayed_graph_generation=delayed_graph_generation)
501
view_revisions = _calc_view_revisions(
502
self.branch, self.start_rev_id, self.end_rev_id,
503
rqst.get('direction'),
504
generate_merge_revisions=generate_merge_revisions,
505
delayed_graph_generation=delayed_graph_generation,
506
exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
456
508
# Apply the other filters
457
509
return make_log_rev_iterator(self.branch, view_revisions,
464
516
# Note that we always generate the merge revisions because
465
517
# filter_revisions_touching_file_id() requires them ...
467
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
468
self.end_rev_id, rqst.get('direction'), True)
519
view_revisions = _calc_view_revisions(
520
self.branch, self.start_rev_id, self.end_rev_id,
521
rqst.get('direction'), generate_merge_revisions=True,
522
exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
469
523
if not isinstance(view_revisions, list):
470
524
view_revisions = list(view_revisions)
471
525
view_revisions = _filter_revisions_touching_file_id(self.branch,
478
532
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
479
generate_merge_revisions, delayed_graph_generation=False):
533
generate_merge_revisions,
534
delayed_graph_generation=False,
535
exclude_common_ancestry=False,
480
537
"""Calculate the revisions to view.
482
539
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
483
540
a list of the same tuples.
542
if (exclude_common_ancestry and start_rev_id == end_rev_id):
543
raise errors.BzrCommandError(
544
'--exclude-common-ancestry requires two different revisions')
545
if direction not in ('reverse', 'forward'):
546
raise ValueError('invalid direction %r' % direction)
485
547
br_revno, br_rev_id = branch.last_revision_info()
486
548
if br_revno == 0:
489
# If a single revision is requested, check we can handle it
490
generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
491
(not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
492
if generate_single_revision:
493
return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
495
# If we only want to see linear revisions, we can iterate ...
496
if not generate_merge_revisions:
497
return _generate_flat_revisions(branch, start_rev_id, end_rev_id,
551
if (end_rev_id and start_rev_id == end_rev_id
552
and (not generate_merge_revisions
553
or not _has_merges(branch, end_rev_id))):
554
# If a single revision is requested, check we can handle it
555
iter_revs = _generate_one_revision(branch, end_rev_id, br_rev_id,
557
elif not generate_merge_revisions:
558
# If we only want to see linear revisions, we can iterate ...
559
iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
560
direction, exclude_common_ancestry)
561
if direction == 'forward':
562
iter_revs = reversed(iter_revs)
500
return _generate_all_revisions(branch, start_rev_id, end_rev_id,
501
direction, delayed_graph_generation)
564
iter_revs = _generate_all_revisions(branch, start_rev_id, end_rev_id,
565
direction, delayed_graph_generation,
566
exclude_common_ancestry)
567
if direction == 'forward':
568
iter_revs = _rebase_merge_depth(reverse_by_depth(list(iter_revs)))
504
572
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
507
575
return [(br_rev_id, br_revno, 0)]
509
revno = branch.revision_id_to_dotted_revno(rev_id)
510
revno_str = '.'.join(str(n) for n in revno)
577
revno_str = _compute_revno_str(branch, rev_id)
511
578
return [(rev_id, revno_str, 0)]
514
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction):
515
result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
581
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
582
exclude_common_ancestry=False):
583
result = _linear_view_revisions(
584
branch, start_rev_id, end_rev_id,
585
exclude_common_ancestry=exclude_common_ancestry)
516
586
# If a start limit was given and it's not obviously an
517
587
# ancestor of the end limit, check it before outputting anything
518
588
if direction == 'forward' or (start_rev_id
522
592
except _StartNotLinearAncestor:
523
593
raise errors.BzrCommandError('Start revision not found in'
524
594
' left-hand history of end revision.')
525
if direction == 'forward':
526
result = reversed(result)
530
598
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
531
delayed_graph_generation):
599
delayed_graph_generation,
600
exclude_common_ancestry=False):
532
601
# On large trees, generating the merge graph can take 30-60 seconds
533
602
# so we delay doing it until a merge is detected, incrementally
534
603
# returning initial (non-merge) revisions while we can.
605
# The above is only true for old formats (<= 0.92), for newer formats, a
606
# couple of seconds only should be needed to load the whole graph and the
607
# other graph operations needed are even faster than that -- vila 100201
535
608
initial_revisions = []
536
609
if delayed_graph_generation:
538
for rev_id, revno, depth in \
539
_linear_view_revisions(branch, start_rev_id, end_rev_id):
611
for rev_id, revno, depth in _linear_view_revisions(
612
branch, start_rev_id, end_rev_id, exclude_common_ancestry):
540
613
if _has_merges(branch, rev_id):
614
# The end_rev_id can be nested down somewhere. We need an
615
# explicit ancestry check. There is an ambiguity here as we
616
# may not raise _StartNotLinearAncestor for a revision that
617
# is an ancestor but not a *linear* one. But since we have
618
# loaded the graph to do the check (or calculate a dotted
619
# revno), we may as well accept to show the log... We need
620
# the check only if start_rev_id is not None as all
621
# revisions have _mod_revision.NULL_REVISION as an ancestor
623
graph = branch.repository.get_graph()
624
if (start_rev_id is not None
625
and not graph.is_ancestor(start_rev_id, end_rev_id)):
626
raise _StartNotLinearAncestor()
627
# Since we collected the revisions so far, we need to
541
629
end_rev_id = rev_id
544
632
initial_revisions.append((rev_id, revno, depth))
546
634
# No merged revisions found
547
if direction == 'reverse':
548
return initial_revisions
549
elif direction == 'forward':
550
return reversed(initial_revisions)
552
raise ValueError('invalid direction %r' % direction)
635
return initial_revisions
553
636
except _StartNotLinearAncestor:
554
637
# A merge was never detected so the lower revision limit can't
555
638
# be nested down somewhere
556
639
raise errors.BzrCommandError('Start revision not found in'
557
640
' history of end revision.')
642
# We exit the loop above because we encounter a revision with merges, from
643
# this revision, we need to switch to _graph_view_revisions.
559
645
# A log including nested merges is required. If the direction is reverse,
560
646
# we rebase the initial merge depths so that the development line is
561
647
# shown naturally, i.e. just like it is for linear logging. We can easily
563
649
# indented at the end seems slightly nicer in that case.
564
650
view_revisions = chain(iter(initial_revisions),
565
651
_graph_view_revisions(branch, start_rev_id, end_rev_id,
566
rebase_initial_depths=direction == 'reverse'))
567
if direction == 'reverse':
568
return view_revisions
569
elif direction == 'forward':
570
# Forward means oldest first, adjusting for depth.
571
view_revisions = reverse_by_depth(list(view_revisions))
572
return _rebase_merge_depth(view_revisions)
574
raise ValueError('invalid direction %r' % direction)
652
rebase_initial_depths=(direction == 'reverse'),
653
exclude_common_ancestry=exclude_common_ancestry))
654
return view_revisions
577
657
def _has_merges(branch, rev_id):
580
660
return len(parents) > 1
663
def _compute_revno_str(branch, rev_id):
664
"""Compute the revno string from a rev_id.
666
:return: The revno string, or None if the revision is not in the supplied
670
revno = branch.revision_id_to_dotted_revno(rev_id)
671
except errors.NoSuchRevision:
672
# The revision must be outside of this branch
675
return '.'.join(str(n) for n in revno)
583
678
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
584
679
"""Is start_rev_id an obvious ancestor of end_rev_id?"""
585
680
if start_rev_id and end_rev_id:
586
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
587
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
682
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
683
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
684
except errors.NoSuchRevision:
685
# one or both is not in the branch; not obvious
588
687
if len(start_dotted) == 1 and len(end_dotted) == 1:
589
688
# both on mainline
590
689
return start_dotted[0] <= end_dotted[0]
697
# if either start or end is not specified then we use either the first or
698
# the last revision and *they* are obvious ancestors.
601
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
702
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
703
exclude_common_ancestry=False):
602
704
"""Calculate a sequence of revisions to view, newest to oldest.
604
706
:param start_rev_id: the lower revision-id
605
707
:param end_rev_id: the upper revision-id
708
:param exclude_common_ancestry: Whether the start_rev_id should be part of
709
the iterated revisions.
606
710
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
607
711
:raises _StartNotLinearAncestor: if a start_rev_id is specified but
608
is not found walking the left-hand history
712
is not found walking the left-hand history
610
714
br_revno, br_rev_id = branch.last_revision_info()
611
715
repo = branch.repository
716
graph = repo.get_graph()
612
717
if start_rev_id is None and end_rev_id is None:
613
718
cur_revno = br_revno
614
for revision_id in repo.iter_reverse_revision_history(br_rev_id):
719
for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
720
(_mod_revision.NULL_REVISION,)):
615
721
yield revision_id, str(cur_revno), 0
618
724
if end_rev_id is None:
619
725
end_rev_id = br_rev_id
620
726
found_start = start_rev_id is None
621
for revision_id in repo.iter_reverse_revision_history(end_rev_id):
622
revno = branch.revision_id_to_dotted_revno(revision_id)
623
revno_str = '.'.join(str(n) for n in revno)
727
for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
728
(_mod_revision.NULL_REVISION,)):
729
revno_str = _compute_revno_str(branch, revision_id)
624
730
if not found_start and revision_id == start_rev_id:
625
yield revision_id, revno_str, 0
731
if not exclude_common_ancestry:
732
yield revision_id, revno_str, 0
626
733
found_start = True
662
774
depth_adjustment = merge_depth
663
775
if depth_adjustment:
664
776
if merge_depth < depth_adjustment:
777
# From now on we reduce the depth adjustement, this can be
778
# surprising for users. The alternative requires two passes
779
# which breaks the fast display of the first revision
665
781
depth_adjustment = merge_depth
666
782
merge_depth -= depth_adjustment
667
783
yield rev_id, '.'.join(map(str, revno)), merge_depth
786
@deprecated_function(deprecated_in((2, 2, 0)))
670
787
def calculate_view_revisions(branch, start_revision, end_revision, direction,
671
788
specific_fileid, generate_merge_revisions):
672
789
"""Calculate the revisions to view.
1263
1380
to indicate which LogRevision attributes it supports:
1265
1382
- supports_delta must be True if this log formatter supports delta.
1266
Otherwise the delta attribute may not be populated. The 'delta_format'
1267
attribute describes whether the 'short_status' format (1) or the long
1268
one (2) should be used.
1383
Otherwise the delta attribute may not be populated. The 'delta_format'
1384
attribute describes whether the 'short_status' format (1) or the long
1385
one (2) should be used.
1270
1387
- supports_merge_revisions must be True if this log formatter supports
1271
merge revisions. If not, then only mainline revisions will be passed
1388
merge revisions. If not, then only mainline revisions will be passed
1274
1391
- preferred_levels is the number of levels this formatter defaults to.
1275
The default value is zero meaning display all levels.
1276
This value is only relevant if supports_merge_revisions is True.
1392
The default value is zero meaning display all levels.
1393
This value is only relevant if supports_merge_revisions is True.
1278
1395
- supports_tags must be True if this log formatter supports tags.
1279
Otherwise the tags attribute may not be populated.
1396
Otherwise the tags attribute may not be populated.
1281
1398
- supports_diff must be True if this log formatter supports diffs.
1282
Otherwise the diff attribute may not be populated.
1399
Otherwise the diff attribute may not be populated.
1401
- supports_signatures must be True if this log formatter supports GPG
1284
1404
Plugins can register functions to show custom revision properties using
1285
1405
the properties_handler_registry. The registered function
1286
must respect the following interface description:
1406
must respect the following interface description::
1287
1408
def my_show_properties(properties_dict):
1288
1409
# code that returns a dict {'name':'value'} of the properties
1291
1412
preferred_levels = 0
1293
1414
def __init__(self, to_file, show_ids=False, show_timezone='original',
1294
delta_format=None, levels=None, show_advice=False):
1415
delta_format=None, levels=None, show_advice=False,
1416
to_exact_file=None, author_list_handler=None):
1295
1417
"""Create a LogFormatter.
1297
1419
:param to_file: the file to output to
1420
:param to_exact_file: if set, gives an output stream to which
1421
non-Unicode diffs are written.
1298
1422
:param show_ids: if True, revision-ids are to be displayed
1299
1423
:param show_timezone: the timezone to use
1300
1424
:param delta_format: the level of delta information to display
1303
1427
let the log formatter decide.
1304
1428
:param show_advice: whether to show advice at the end of the
1430
:param author_list_handler: callable generating a list of
1431
authors to display for a given revision
1307
1433
self.to_file = to_file
1308
1434
# 'exact' stream used to show diff, it should print content 'as is'
1309
1435
# and should not try to decode/encode it to unicode to avoid bug #328007
1310
self.to_exact_file = getattr(to_file, 'stream', to_file)
1436
if to_exact_file is not None:
1437
self.to_exact_file = to_exact_file
1439
# XXX: somewhat hacky; this assumes it's a codec writer; it's better
1440
# for code that expects to get diffs to pass in the exact file
1442
self.to_exact_file = getattr(to_file, 'stream', to_file)
1311
1443
self.show_ids = show_ids
1312
1444
self.show_timezone = show_timezone
1313
1445
if delta_format is None:
1356
1489
def short_author(self, rev):
1357
name, address = config.parse_username(rev.get_apparent_authors()[0])
1490
return self.authors(rev, 'first', short=True, sep=', ')
1492
def authors(self, rev, who, short=False, sep=None):
1493
"""Generate list of authors, taking --authors option into account.
1495
The caller has to specify the name of a author list handler,
1496
as provided by the author list registry, using the ``who``
1497
argument. That name only sets a default, though: when the
1498
user selected a different author list generation using the
1499
``--authors`` command line switch, as represented by the
1500
``author_list_handler`` constructor argument, that value takes
1503
:param rev: The revision for which to generate the list of authors.
1504
:param who: Name of the default handler.
1505
:param short: Whether to shorten names to either name or address.
1506
:param sep: What separator to use for automatic concatenation.
1508
if self._author_list_handler is not None:
1509
# The user did specify --authors, which overrides the default
1510
author_list_handler = self._author_list_handler
1512
# The user didn't specify --authors, so we use the caller's default
1513
author_list_handler = author_list_registry.get(who)
1514
names = author_list_handler(rev)
1516
for i in range(len(names)):
1517
name, address = config.parse_username(names[i])
1523
names = sep.join(names)
1362
1526
def merge_marker(self, revision):
1363
1527
"""Get the merge marker to include in the output or '' if none."""
1370
def show_foreign_info(self, rev, indent):
1534
def show_properties(self, revision, indent):
1535
"""Displays the custom properties returned by each registered handler.
1537
If a registered handler raises an error it is propagated.
1539
for line in self.custom_properties(revision):
1540
self.to_file.write("%s%s\n" % (indent, line))
1542
def custom_properties(self, revision):
1543
"""Format the custom properties returned by each registered handler.
1545
If a registered handler raises an error it is propagated.
1547
:return: a list of formatted lines (excluding trailing newlines)
1549
lines = self._foreign_info_properties(revision)
1550
for key, handler in properties_handler_registry.iteritems():
1551
lines.extend(self._format_properties(handler(revision)))
1554
def _foreign_info_properties(self, rev):
1371
1555
"""Custom log displayer for foreign revision identifiers.
1373
1557
:param rev: Revision object.
1375
1559
# Revision comes directly from a foreign repository
1376
1560
if isinstance(rev, foreign.ForeignRevision):
1377
self._write_properties(indent, rev.mapping.vcs.show_foreign_revid(
1561
return self._format_properties(
1562
rev.mapping.vcs.show_foreign_revid(rev.foreign_revid))
1381
1564
# Imported foreign revision revision ids always contain :
1382
1565
if not ":" in rev.revision_id:
1385
1568
# Revision was once imported from a foreign repository
1387
1570
foreign_revid, mapping = \
1388
1571
foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1389
1572
except errors.InvalidRevisionId:
1392
self._write_properties(indent,
1575
return self._format_properties(
1393
1576
mapping.vcs.show_foreign_revid(foreign_revid))
1395
def show_properties(self, revision, indent):
1396
"""Displays the custom properties returned by each registered handler.
1398
If a registered handler raises an error it is propagated.
1400
for key, handler in properties_handler_registry.iteritems():
1401
self._write_properties(indent, handler(revision))
1403
def _write_properties(self, indent, properties):
1578
def _format_properties(self, properties):
1404
1580
for key, value in properties.items():
1405
self.to_file.write(indent + key + ': ' + value + '\n')
1581
lines.append(key + ': ' + value)
1407
1584
def show_diff(self, to_file, diff, indent):
1408
1585
for l in diff.rstrip().split('\n'):
1409
1586
to_file.write(indent + '%s\n' % (l,))
1589
# Separator between revisions in long format
1590
_LONG_SEP = '-' * 60
1412
1593
class LongLogFormatter(LogFormatter):
1414
1595
supports_merge_revisions = True
1416
1597
supports_delta = True
1417
1598
supports_tags = True
1418
1599
supports_diff = True
1600
supports_signatures = True
1602
def __init__(self, *args, **kwargs):
1603
super(LongLogFormatter, self).__init__(*args, **kwargs)
1604
if self.show_timezone == 'original':
1605
self.date_string = self._date_string_original_timezone
1607
self.date_string = self._date_string_with_timezone
1609
def _date_string_with_timezone(self, rev):
1610
return format_date(rev.timestamp, rev.timezone or 0,
1613
def _date_string_original_timezone(self, rev):
1614
return format_date_with_offset_in_original_timezone(rev.timestamp,
1420
1617
def log_revision(self, revision):
1421
1618
"""Log a revision, either merged or not."""
1422
1619
indent = ' ' * revision.merge_depth
1423
to_file = self.to_file
1424
to_file.write(indent + '-' * 60 + '\n')
1425
1621
if revision.revno is not None:
1426
to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
1622
lines.append('revno: %s%s' % (revision.revno,
1427
1623
self.merge_marker(revision)))
1428
1624
if revision.tags:
1429
to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
1625
lines.append('tags: %s' % (', '.join(revision.tags)))
1626
if self.show_ids or revision.revno is None:
1627
lines.append('revision-id: %s' % (revision.rev.revision_id,))
1430
1628
if self.show_ids:
1431
to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
1433
1629
for parent_id in revision.rev.parent_ids:
1434
to_file.write(indent + 'parent: %s\n' % (parent_id,))
1435
self.show_foreign_info(revision.rev, indent)
1436
self.show_properties(revision.rev, indent)
1630
lines.append('parent: %s' % (parent_id,))
1631
lines.extend(self.custom_properties(revision.rev))
1438
1633
committer = revision.rev.committer
1439
authors = revision.rev.get_apparent_authors()
1634
authors = self.authors(revision.rev, 'all')
1440
1635
if authors != [committer]:
1441
to_file.write(indent + 'author: %s\n' % (", ".join(authors),))
1442
to_file.write(indent + 'committer: %s\n' % (committer,))
1636
lines.append('author: %s' % (", ".join(authors),))
1637
lines.append('committer: %s' % (committer,))
1444
1639
branch_nick = revision.rev.properties.get('branch-nick', None)
1445
1640
if branch_nick is not None:
1446
to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))
1448
date_str = format_date(revision.rev.timestamp,
1449
revision.rev.timezone or 0,
1451
to_file.write(indent + 'timestamp: %s\n' % (date_str,))
1453
to_file.write(indent + 'message:\n')
1641
lines.append('branch nick: %s' % (branch_nick,))
1643
lines.append('timestamp: %s' % (self.date_string(revision.rev),))
1645
if revision.signature is not None:
1646
lines.append('signature: ' + revision.signature)
1648
lines.append('message:')
1454
1649
if not revision.rev.message:
1455
to_file.write(indent + ' (no message)\n')
1650
lines.append(' (no message)')
1457
1652
message = revision.rev.message.rstrip('\r\n')
1458
1653
for l in message.split('\n'):
1459
to_file.write(indent + ' %s\n' % (l,))
1654
lines.append(' %s' % (l,))
1656
# Dump the output, appending the delta and diff if requested
1657
to_file = self.to_file
1658
to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
1460
1659
if revision.delta is not None:
1461
# We don't respect delta_format for compatibility
1462
revision.delta.show(to_file, self.show_ids, indent=indent,
1660
# Use the standard status output to display changes
1661
from bzrlib.delta import report_delta
1662
report_delta(to_file, revision.delta, short_status=False,
1663
show_ids=self.show_ids, indent=indent)
1464
1664
if revision.diff is not None:
1465
1665
to_file.write(indent + 'diff:\n')
1466
1667
# Note: we explicitly don't indent the diff (relative to the
1467
1668
# revision information) so that the output can be fed to patch -p0
1468
1669
self.show_diff(self.to_exact_file, revision.diff, indent)
1670
self.to_exact_file.flush()
1470
1672
def get_advice_separator(self):
1471
1673
"""Get the text separating the log from the closing advice."""
1509
1711
if revision.tags:
1510
1712
tags = ' {%s}' % (', '.join(revision.tags))
1511
1713
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1512
revision.revno, self.short_author(revision.rev),
1714
revision.revno or "", self.short_author(revision.rev),
1513
1715
format_date(revision.rev.timestamp,
1514
1716
revision.rev.timezone or 0,
1515
1717
self.show_timezone, date_fmt="%Y-%m-%d",
1516
1718
show_offset=False),
1517
1719
tags, self.merge_marker(revision)))
1518
self.show_foreign_info(revision.rev, indent+offset)
1519
1720
self.show_properties(revision.rev, indent+offset)
1721
if self.show_ids or revision.revno is None:
1521
1722
to_file.write(indent + offset + 'revision-id:%s\n'
1522
1723
% (revision.rev.revision_id,))
1523
1724
if not revision.rev.message:
1570
1778
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1571
1779
"""Format log info into one string. Truncate tail of string
1572
:param revno: revision number or None.
1573
Revision numbers counts from 1.
1574
:param rev: revision object
1575
:param max_chars: maximum length of resulting string
1576
:param tags: list of tags or None
1577
:param prefix: string to prefix each line
1578
:return: formatted truncated string
1781
:param revno: revision number or None.
1782
Revision numbers counts from 1.
1783
:param rev: revision object
1784
:param max_chars: maximum length of resulting string
1785
:param tags: list of tags or None
1786
:param prefix: string to prefix each line
1787
:return: formatted truncated string
1582
1791
# show revno only when is not None
1583
1792
out.append("%s:" % revno)
1584
out.append(self.truncate(self.short_author(rev), 20))
1793
if max_chars is not None:
1794
out.append(self.truncate(self.short_author(rev), (max_chars+3)/4))
1796
out.append(self.short_author(rev))
1585
1797
out.append(self.date_string(rev))
1586
1798
if len(rev.parent_ids) > 1:
1587
1799
out.append('[merge]')
1677
1890
raise errors.BzrCommandError("unknown log formatter: %r" % name)
1893
def author_list_all(rev):
1894
return rev.get_apparent_authors()[:]
1897
def author_list_first(rev):
1898
lst = rev.get_apparent_authors()
1905
def author_list_committer(rev):
1906
return [rev.committer]
1909
author_list_registry = registry.Registry()
1911
author_list_registry.register('all', author_list_all,
1914
author_list_registry.register('first', author_list_first,
1917
author_list_registry.register('committer', author_list_committer,
1680
1921
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1681
1922
# deprecated; for compatibility
1682
1923
lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1843
2085
:param file_list: the list of paths given on the command line;
1844
2086
the first of these can be a branch location or a file path,
1845
2087
the remainder must be file paths
2088
:param add_cleanup: When the branch returned is read locked,
2089
an unlock call will be queued to the cleanup.
1846
2090
:return: (branch, info_list, start_rev_info, end_rev_info) where
1847
2091
info_list is a list of (relative_path, file_id, kind) tuples where
1848
2092
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2093
branch will be read-locked.
1850
from builtins import _get_revision_range, safe_relpath_files
2095
from builtins import _get_revision_range
1851
2096
tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2097
add_cleanup(b.lock_read().unlock)
1852
2098
# XXX: It's damn messy converting a list of paths to relative paths when
1853
2099
# those paths might be deleted ones, they might be on a case-insensitive
1854
2100
# filesystem and/or they might be in silly locations (like another branch).