221
215
'direction': 'reverse',
223
217
'generate_tags': True,
224
'exclude_common_ancestry': False,
225
218
'_match_using_deltas': True,
229
222
def make_log_request_dict(direction='reverse', specific_fileids=None,
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,
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):
237
226
"""Convenience function for making a logging request dictionary.
239
228
Using this function may make code slightly safer by ensuring
304
286
def _apply_log_request_defaults(rqst):
305
287
"""Apply default values to a request dictionary."""
306
result = _DEFAULT_REQUEST_PARAMS.copy()
288
result = _DEFAULT_REQUEST_PARAMS
308
290
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"
333
294
class LogGenerator(object):
334
295
"""A generator of log revisions."""
425
384
: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')
433
388
revision_iterator = self._create_log_revision_iterator()
434
389
for revs in revision_iterator:
435
390
for (rev_id, revno, merge_depth), rev, delta in revs:
436
391
# 0 levels means show everything; merge_depth counts from 0
392
levels = rqst.get('levels')
437
393
if levels != 0 and merge_depth >= levels:
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)
395
diff = self._format_diff(rev, rev_id)
448
396
yield LogRevision(rev, revno, merge_depth, delta,
449
self.rev_tag_dict.get(rev_id), diff, signature)
397
self.rev_tag_dict.get(rev_id), diff)
398
limit = rqst.get('limit')
452
401
if log_count >= limit:
455
def _format_diff(self, rev, rev_id, diff_type):
404
def _format_diff(self, rev, rev_id):
405
diff_type = self.rqst.get('diff_type')
406
if diff_type is None:
456
408
repo = self.branch.repository
457
409
if len(rev.parent_ids) == 0:
458
410
ancestor_id = _mod_revision.NULL_REVISION
498
449
generate_merge_revisions = rqst.get('levels') != 1
499
450
delayed_graph_generation = not rqst.get('specific_fileids') and (
500
451
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
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'))
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)
508
456
# Apply the other filters
509
457
return make_log_rev_iterator(self.branch, view_revisions,
516
464
# Note that we always generate the merge revisions because
517
465
# filter_revisions_touching_file_id() requires them ...
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'))
467
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
468
self.end_rev_id, rqst.get('direction'), True)
523
469
if not isinstance(view_revisions, list):
524
470
view_revisions = list(view_revisions)
525
471
view_revisions = _filter_revisions_touching_file_id(self.branch,
532
478
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
533
generate_merge_revisions,
534
delayed_graph_generation=False,
535
exclude_common_ancestry=False,
479
generate_merge_revisions, delayed_graph_generation=False):
537
480
"""Calculate the revisions to view.
539
482
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
540
483
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)
547
485
br_revno, br_rev_id = branch.last_revision_info()
548
486
if br_revno == 0:
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)
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,
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)))
500
return _generate_all_revisions(branch, start_rev_id, end_rev_id,
501
direction, delayed_graph_generation)
572
504
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
575
507
return [(br_rev_id, br_revno, 0)]
577
revno_str = _compute_revno_str(branch, rev_id)
509
revno = branch.revision_id_to_dotted_revno(rev_id)
510
revno_str = '.'.join(str(n) for n in revno)
578
511
return [(rev_id, revno_str, 0)]
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)
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)
586
516
# If a start limit was given and it's not obviously an
587
517
# ancestor of the end limit, check it before outputting anything
588
518
if direction == 'forward' or (start_rev_id
592
522
except _StartNotLinearAncestor:
593
523
raise errors.BzrCommandError('Start revision not found in'
594
524
' left-hand history of end revision.')
525
if direction == 'forward':
526
result = reversed(result)
598
530
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
599
delayed_graph_generation,
600
exclude_common_ancestry=False):
531
delayed_graph_generation):
601
532
# On large trees, generating the merge graph can take 30-60 seconds
602
533
# so we delay doing it until a merge is detected, incrementally
603
534
# 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
608
535
initial_revisions = []
609
536
if delayed_graph_generation:
611
for rev_id, revno, depth in _linear_view_revisions(
612
branch, start_rev_id, end_rev_id, exclude_common_ancestry):
538
for rev_id, revno, depth in \
539
_linear_view_revisions(branch, start_rev_id, end_rev_id):
613
540
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
629
541
end_rev_id = rev_id
632
544
initial_revisions.append((rev_id, revno, depth))
634
546
# No merged revisions found
635
return initial_revisions
547
if direction == 'reverse':
548
return initial_revisions
549
elif direction == 'forward':
550
return reversed(initial_revisions)
552
raise ValueError('invalid direction %r' % direction)
636
553
except _StartNotLinearAncestor:
637
554
# A merge was never detected so the lower revision limit can't
638
555
# be nested down somewhere
639
556
raise errors.BzrCommandError('Start revision not found in'
640
557
' 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.
645
559
# A log including nested merges is required. If the direction is reverse,
646
560
# we rebase the initial merge depths so that the development line is
647
561
# shown naturally, i.e. just like it is for linear logging. We can easily
649
563
# indented at the end seems slightly nicer in that case.
650
564
view_revisions = chain(iter(initial_revisions),
651
565
_graph_view_revisions(branch, start_rev_id, end_rev_id,
652
rebase_initial_depths=(direction == 'reverse'),
653
exclude_common_ancestry=exclude_common_ancestry))
654
return view_revisions
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)
657
577
def _has_merges(branch, rev_id):
660
580
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)
678
583
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
679
584
"""Is start_rev_id an obvious ancestor of end_rev_id?"""
680
585
if start_rev_id and 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
586
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
587
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
687
588
if len(start_dotted) == 1 and len(end_dotted) == 1:
688
589
# both on mainline
689
590
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.
702
def _linear_view_revisions(branch, start_rev_id, end_rev_id,
703
exclude_common_ancestry=False):
601
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
704
602
"""Calculate a sequence of revisions to view, newest to oldest.
706
604
:param start_rev_id: the lower revision-id
707
605
: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.
710
606
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
711
607
:raises _StartNotLinearAncestor: if a start_rev_id is specified but
712
is not found walking the left-hand history
608
is not found walking the left-hand history
714
610
br_revno, br_rev_id = branch.last_revision_info()
715
611
repo = branch.repository
716
graph = repo.get_graph()
717
612
if start_rev_id is None and end_rev_id is None:
718
613
cur_revno = br_revno
719
for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
720
(_mod_revision.NULL_REVISION,)):
614
for revision_id in repo.iter_reverse_revision_history(br_rev_id):
721
615
yield revision_id, str(cur_revno), 0
724
618
if end_rev_id is None:
725
619
end_rev_id = br_rev_id
726
620
found_start = start_rev_id is None
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)
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)
730
624
if not found_start and revision_id == start_rev_id:
731
if not exclude_common_ancestry:
732
yield revision_id, revno_str, 0
625
yield revision_id, revno_str, 0
733
626
found_start = True
774
662
depth_adjustment = merge_depth
775
663
if depth_adjustment:
776
664
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
781
665
depth_adjustment = merge_depth
782
666
merge_depth -= depth_adjustment
783
667
yield rev_id, '.'.join(map(str, revno)), merge_depth
786
@deprecated_function(deprecated_in((2, 2, 0)))
787
670
def calculate_view_revisions(branch, start_revision, end_revision, direction,
788
671
specific_fileid, generate_merge_revisions):
789
672
"""Calculate the revisions to view.
1380
1263
to indicate which LogRevision attributes it supports:
1382
1265
- supports_delta must be True if this log formatter supports delta.
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.
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.
1387
1270
- supports_merge_revisions must be True if this log formatter supports
1388
merge revisions. If not, then only mainline revisions will be passed
1271
merge revisions. If not, then only mainline revisions will be passed
1391
1274
- preferred_levels is the number of levels this formatter defaults to.
1392
The default value is zero meaning display all levels.
1393
This value is only relevant if supports_merge_revisions is True.
1275
The default value is zero meaning display all levels.
1276
This value is only relevant if supports_merge_revisions is True.
1395
1278
- supports_tags must be True if this log formatter supports tags.
1396
Otherwise the tags attribute may not be populated.
1279
Otherwise the tags attribute may not be populated.
1398
1281
- supports_diff must be True if this log formatter supports diffs.
1399
Otherwise the diff attribute may not be populated.
1401
- supports_signatures must be True if this log formatter supports GPG
1282
Otherwise the diff attribute may not be populated.
1404
1284
Plugins can register functions to show custom revision properties using
1405
1285
the properties_handler_registry. The registered function
1406
must respect the following interface description::
1286
must respect the following interface description:
1408
1287
def my_show_properties(properties_dict):
1409
1288
# code that returns a dict {'name':'value'} of the properties
1412
1291
preferred_levels = 0
1414
1293
def __init__(self, to_file, show_ids=False, show_timezone='original',
1415
delta_format=None, levels=None, show_advice=False,
1416
to_exact_file=None, author_list_handler=None):
1294
delta_format=None, levels=None, show_advice=False):
1417
1295
"""Create a LogFormatter.
1419
1297
: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.
1422
1298
:param show_ids: if True, revision-ids are to be displayed
1423
1299
:param show_timezone: the timezone to use
1424
1300
:param delta_format: the level of delta information to display
1427
1303
let the log formatter decide.
1428
1304
: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
1433
1307
self.to_file = to_file
1434
1308
# 'exact' stream used to show diff, it should print content 'as is'
1435
1309
# and should not try to decode/encode it to unicode to avoid bug #328007
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)
1310
self.to_exact_file = getattr(to_file, 'stream', to_file)
1443
1311
self.show_ids = show_ids
1444
1312
self.show_timezone = show_timezone
1445
1313
if delta_format is None:
1489
1356
def short_author(self, rev):
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)
1357
name, address = config.parse_username(rev.get_apparent_authors()[0])
1526
1362
def merge_marker(self, revision):
1527
1363
"""Get the merge marker to include in the output or '' if none."""
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):
1370
def show_foreign_info(self, rev, indent):
1555
1371
"""Custom log displayer for foreign revision identifiers.
1557
1373
:param rev: Revision object.
1559
1375
# Revision comes directly from a foreign repository
1560
1376
if isinstance(rev, foreign.ForeignRevision):
1561
return self._format_properties(
1562
rev.mapping.vcs.show_foreign_revid(rev.foreign_revid))
1377
self._write_properties(indent, rev.mapping.vcs.show_foreign_revid(
1564
1381
# Imported foreign revision revision ids always contain :
1565
1382
if not ":" in rev.revision_id:
1568
1385
# Revision was once imported from a foreign repository
1570
1387
foreign_revid, mapping = \
1571
1388
foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1572
1389
except errors.InvalidRevisionId:
1575
return self._format_properties(
1392
self._write_properties(indent,
1576
1393
mapping.vcs.show_foreign_revid(foreign_revid))
1578
def _format_properties(self, properties):
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):
1580
1404
for key, value in properties.items():
1581
lines.append(key + ': ' + value)
1405
self.to_file.write(indent + key + ': ' + value + '\n')
1584
1407
def show_diff(self, to_file, diff, indent):
1585
1408
for l in diff.rstrip().split('\n'):
1586
1409
to_file.write(indent + '%s\n' % (l,))
1589
# Separator between revisions in long format
1590
_LONG_SEP = '-' * 60
1593
1412
class LongLogFormatter(LogFormatter):
1595
1414
supports_merge_revisions = True
1597
1416
supports_delta = True
1598
1417
supports_tags = True
1599
1418
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,
1617
1420
def log_revision(self, revision):
1618
1421
"""Log a revision, either merged or not."""
1619
1422
indent = ' ' * revision.merge_depth
1423
to_file = self.to_file
1424
to_file.write(indent + '-' * 60 + '\n')
1621
1425
if revision.revno is not None:
1622
lines.append('revno: %s%s' % (revision.revno,
1426
to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
1623
1427
self.merge_marker(revision)))
1624
1428
if 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,))
1429
to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
1628
1430
if self.show_ids:
1431
to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
1629
1433
for parent_id in revision.rev.parent_ids:
1630
lines.append('parent: %s' % (parent_id,))
1631
lines.extend(self.custom_properties(revision.rev))
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)
1633
1438
committer = revision.rev.committer
1634
authors = self.authors(revision.rev, 'all')
1439
authors = revision.rev.get_apparent_authors()
1635
1440
if authors != [committer]:
1636
lines.append('author: %s' % (", ".join(authors),))
1637
lines.append('committer: %s' % (committer,))
1441
to_file.write(indent + 'author: %s\n' % (", ".join(authors),))
1442
to_file.write(indent + 'committer: %s\n' % (committer,))
1639
1444
branch_nick = revision.rev.properties.get('branch-nick', None)
1640
1445
if branch_nick is not None:
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:')
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')
1649
1454
if not revision.rev.message:
1650
lines.append(' (no message)')
1455
to_file.write(indent + ' (no message)\n')
1652
1457
message = revision.rev.message.rstrip('\r\n')
1653
1458
for l in message.split('\n'):
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)))
1459
to_file.write(indent + ' %s\n' % (l,))
1659
1460
if revision.delta is not None:
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)
1461
# We don't respect delta_format for compatibility
1462
revision.delta.show(to_file, self.show_ids, indent=indent,
1664
1464
if revision.diff is not None:
1665
1465
to_file.write(indent + 'diff:\n')
1667
1466
# Note: we explicitly don't indent the diff (relative to the
1668
1467
# revision information) so that the output can be fed to patch -p0
1669
1468
self.show_diff(self.to_exact_file, revision.diff, indent)
1670
self.to_exact_file.flush()
1672
1470
def get_advice_separator(self):
1673
1471
"""Get the text separating the log from the closing advice."""
1711
1509
if revision.tags:
1712
1510
tags = ' {%s}' % (', '.join(revision.tags))
1713
1511
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1714
revision.revno or "", self.short_author(revision.rev),
1512
revision.revno, self.short_author(revision.rev),
1715
1513
format_date(revision.rev.timestamp,
1716
1514
revision.rev.timezone or 0,
1717
1515
self.show_timezone, date_fmt="%Y-%m-%d",
1718
1516
show_offset=False),
1719
1517
tags, self.merge_marker(revision)))
1518
self.show_foreign_info(revision.rev, indent+offset)
1720
1519
self.show_properties(revision.rev, indent+offset)
1721
if self.show_ids or revision.revno is None:
1722
1521
to_file.write(indent + offset + 'revision-id:%s\n'
1723
1522
% (revision.rev.revision_id,))
1724
1523
if not revision.rev.message:
1778
1570
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1779
1571
"""Format log info into one string. Truncate tail of 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
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
1791
1582
# show revno only when is not None
1792
1583
out.append("%s:" % revno)
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))
1584
out.append(self.truncate(self.short_author(rev), 20))
1797
1585
out.append(self.date_string(rev))
1798
1586
if len(rev.parent_ids) > 1:
1799
1587
out.append('[merge]')
1890
1677
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,
1921
1680
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1922
1681
# deprecated; for compatibility
1923
1682
lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
2085
1843
:param file_list: the list of paths given on the command line;
2086
1844
the first of these can be a branch location or a file path,
2087
1845
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.
2090
1846
:return: (branch, info_list, start_rev_info, end_rev_info) where
2091
1847
info_list is a list of (relative_path, file_id, kind) tuples where
2092
1848
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2093
branch will be read-locked.
2095
from builtins import _get_revision_range
1850
from builtins import _get_revision_range, safe_relpath_files
2096
1851
tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2097
add_cleanup(b.lock_read().unlock)
2098
1852
# XXX: It's damn messy converting a list of paths to relative paths when
2099
1853
# those paths might be deleted ones, they might be on a case-insensitive
2100
1854
# filesystem and/or they might be in silly locations (like another branch).