221
214
'direction': 'reverse',
223
216
'generate_tags': True,
224
'exclude_common_ancestry': False,
225
217
'_match_using_deltas': True,
229
221
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,
222
start_revision=None, end_revision=None, limit=None,
223
message_search=None, levels=1, generate_tags=True, delta_type=None,
224
diff_type=None, _match_using_deltas=True):
237
225
"""Convenience function for making a logging request dictionary.
239
227
Using this function may make code slightly safer by ensuring
304
285
def _apply_log_request_defaults(rqst):
305
286
"""Apply default values to a request dictionary."""
306
result = _DEFAULT_REQUEST_PARAMS.copy()
287
result = _DEFAULT_REQUEST_PARAMS
308
289
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
293
class LogGenerator(object):
334
294
"""A generator of log revisions."""
425
383
: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
387
revision_iterator = self._create_log_revision_iterator()
434
388
for revs in revision_iterator:
435
389
for (rev_id, revno, merge_depth), rev, delta in revs:
436
390
# 0 levels means show everything; merge_depth counts from 0
391
levels = rqst.get('levels')
437
392
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)
394
diff = self._format_diff(rev, rev_id)
448
395
yield LogRevision(rev, revno, merge_depth, delta,
449
self.rev_tag_dict.get(rev_id), diff, signature)
396
self.rev_tag_dict.get(rev_id), diff)
397
limit = rqst.get('limit')
452
400
if log_count >= limit:
455
def _format_diff(self, rev, rev_id, diff_type):
403
def _format_diff(self, rev, rev_id):
404
diff_type = self.rqst.get('diff_type')
405
if diff_type is None:
456
407
repo = self.branch.repository
457
408
if len(rev.parent_ids) == 0:
458
409
ancestor_id = _mod_revision.NULL_REVISION
498
448
generate_merge_revisions = rqst.get('levels') != 1
499
449
delayed_graph_generation = not rqst.get('specific_fileids') and (
500
450
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'))
451
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
452
self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
453
delayed_graph_generation=delayed_graph_generation)
508
455
# Apply the other filters
509
456
return make_log_rev_iterator(self.branch, view_revisions,
516
463
# Note that we always generate the merge revisions because
517
464
# 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'))
466
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
467
self.end_rev_id, rqst.get('direction'), True)
523
468
if not isinstance(view_revisions, list):
524
469
view_revisions = list(view_revisions)
525
470
view_revisions = _filter_revisions_touching_file_id(self.branch,
532
477
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,
478
generate_merge_revisions, delayed_graph_generation=False):
537
479
"""Calculate the revisions to view.
539
481
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
540
482
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
484
br_revno, br_rev_id = branch.last_revision_info()
548
485
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)
488
# If a single revision is requested, check we can handle it
489
generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
490
(not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
491
if generate_single_revision:
492
return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
494
# If we only want to see linear revisions, we can iterate ...
495
if not generate_merge_revisions:
496
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)))
499
return _generate_all_revisions(branch, start_rev_id, end_rev_id,
500
direction, delayed_graph_generation)
572
503
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
575
506
return [(br_rev_id, br_revno, 0)]
577
revno_str = _compute_revno_str(branch, rev_id)
508
revno = branch.revision_id_to_dotted_revno(rev_id)
509
revno_str = '.'.join(str(n) for n in revno)
578
510
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)
513
def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction):
514
result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
586
515
# If a start limit was given and it's not obviously an
587
516
# ancestor of the end limit, check it before outputting anything
588
517
if direction == 'forward' or (start_rev_id
592
521
except _StartNotLinearAncestor:
593
522
raise errors.BzrCommandError('Start revision not found in'
594
523
' left-hand history of end revision.')
524
if direction == 'forward':
525
result = reversed(result)
598
529
def _generate_all_revisions(branch, start_rev_id, end_rev_id, direction,
599
delayed_graph_generation,
600
exclude_common_ancestry=False):
530
delayed_graph_generation):
601
531
# On large trees, generating the merge graph can take 30-60 seconds
602
532
# so we delay doing it until a merge is detected, incrementally
603
533
# 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
534
initial_revisions = []
609
535
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):
537
for rev_id, revno, depth in \
538
_linear_view_revisions(branch, start_rev_id, end_rev_id):
613
539
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
540
end_rev_id = rev_id
632
543
initial_revisions.append((rev_id, revno, depth))
634
545
# No merged revisions found
635
return initial_revisions
546
if direction == 'reverse':
547
return initial_revisions
548
elif direction == 'forward':
549
return reversed(initial_revisions)
551
raise ValueError('invalid direction %r' % direction)
636
552
except _StartNotLinearAncestor:
637
553
# A merge was never detected so the lower revision limit can't
638
554
# be nested down somewhere
639
555
raise errors.BzrCommandError('Start revision not found in'
640
556
' 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
558
# A log including nested merges is required. If the direction is reverse,
646
559
# we rebase the initial merge depths so that the development line is
647
560
# shown naturally, i.e. just like it is for linear logging. We can easily
649
562
# indented at the end seems slightly nicer in that case.
650
563
view_revisions = chain(iter(initial_revisions),
651
564
_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
565
rebase_initial_depths=direction == 'reverse'))
566
if direction == 'reverse':
567
return view_revisions
568
elif direction == 'forward':
569
# Forward means oldest first, adjusting for depth.
570
view_revisions = reverse_by_depth(list(view_revisions))
571
return _rebase_merge_depth(view_revisions)
573
raise ValueError('invalid direction %r' % direction)
657
576
def _has_merges(branch, rev_id):
660
579
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
582
def _is_obvious_ancestor(branch, start_rev_id, end_rev_id):
679
583
"""Is start_rev_id an obvious ancestor of end_rev_id?"""
680
584
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
585
start_dotted = branch.revision_id_to_dotted_revno(start_rev_id)
586
end_dotted = branch.revision_id_to_dotted_revno(end_rev_id)
687
587
if len(start_dotted) == 1 and len(end_dotted) == 1:
688
588
# both on mainline
689
589
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):
600
def _linear_view_revisions(branch, start_rev_id, end_rev_id):
704
601
"""Calculate a sequence of revisions to view, newest to oldest.
706
603
:param start_rev_id: the lower revision-id
707
604
: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
605
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
711
606
:raises _StartNotLinearAncestor: if a start_rev_id is specified but
712
is not found walking the left-hand history
607
is not found walking the left-hand history
714
609
br_revno, br_rev_id = branch.last_revision_info()
715
610
repo = branch.repository
716
graph = repo.get_graph()
717
611
if start_rev_id is None and end_rev_id is None:
718
612
cur_revno = br_revno
719
for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
720
(_mod_revision.NULL_REVISION,)):
613
for revision_id in repo.iter_reverse_revision_history(br_rev_id):
721
614
yield revision_id, str(cur_revno), 0
724
617
if end_rev_id is None:
725
618
end_rev_id = br_rev_id
726
619
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)
620
for revision_id in repo.iter_reverse_revision_history(end_rev_id):
621
revno = branch.revision_id_to_dotted_revno(revision_id)
622
revno_str = '.'.join(str(n) for n in revno)
730
623
if not found_start and revision_id == start_rev_id:
731
if not exclude_common_ancestry:
732
yield revision_id, revno_str, 0
624
yield revision_id, revno_str, 0
733
625
found_start = True
774
661
depth_adjustment = merge_depth
775
662
if depth_adjustment:
776
663
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
664
depth_adjustment = merge_depth
782
665
merge_depth -= depth_adjustment
783
666
yield rev_id, '.'.join(map(str, revno)), merge_depth
786
@deprecated_function(deprecated_in((2, 2, 0)))
787
669
def calculate_view_revisions(branch, start_revision, end_revision, direction,
788
670
specific_fileid, generate_merge_revisions):
789
671
"""Calculate the revisions to view.
1380
1262
to indicate which LogRevision attributes it supports:
1382
1264
- 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.
1265
Otherwise the delta attribute may not be populated. The 'delta_format'
1266
attribute describes whether the 'short_status' format (1) or the long
1267
one (2) should be used.
1387
1269
- supports_merge_revisions must be True if this log formatter supports
1388
merge revisions. If not, then only mainline revisions will be passed
1270
merge revisions. If not, then only mainline revisions will be passed
1391
1273
- 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.
1274
The default value is zero meaning display all levels.
1275
This value is only relevant if supports_merge_revisions is True.
1395
1277
- supports_tags must be True if this log formatter supports tags.
1396
Otherwise the tags attribute may not be populated.
1278
Otherwise the tags attribute may not be populated.
1398
1280
- 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
1281
Otherwise the diff attribute may not be populated.
1404
1283
Plugins can register functions to show custom revision properties using
1405
1284
the properties_handler_registry. The registered function
1406
must respect the following interface description::
1285
must respect the following interface description:
1408
1286
def my_show_properties(properties_dict):
1409
1287
# code that returns a dict {'name':'value'} of the properties
1412
1290
preferred_levels = 0
1414
1292
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):
1293
delta_format=None, levels=None, show_advice=False):
1417
1294
"""Create a LogFormatter.
1419
1296
: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
1297
:param show_ids: if True, revision-ids are to be displayed
1423
1298
:param show_timezone: the timezone to use
1424
1299
:param delta_format: the level of delta information to display
1427
1302
let the log formatter decide.
1428
1303
: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
1306
self.to_file = to_file
1434
1307
# 'exact' stream used to show diff, it should print content 'as is'
1435
1308
# 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)
1309
self.to_exact_file = getattr(to_file, 'stream', to_file)
1443
1310
self.show_ids = show_ids
1444
1311
self.show_timezone = show_timezone
1445
1312
if delta_format is None:
1489
1355
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)
1356
name, address = config.parse_username(rev.get_apparent_authors()[0])
1526
1361
def merge_marker(self, revision):
1527
1362
"""Get the merge marker to include in the output or '' if none."""
1537
1372
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
1374
for key, handler in properties_handler_registry.iteritems():
1551
lines.extend(self._format_properties(handler(revision)))
1554
def _foreign_info_properties(self, rev):
1555
"""Custom log displayer for foreign revision identifiers.
1557
:param rev: Revision object.
1559
# Revision comes directly from a foreign repository
1560
if isinstance(rev, foreign.ForeignRevision):
1561
return self._format_properties(
1562
rev.mapping.vcs.show_foreign_revid(rev.foreign_revid))
1564
# Imported foreign revision revision ids always contain :
1565
if not ":" in rev.revision_id:
1568
# Revision was once imported from a foreign repository
1570
foreign_revid, mapping = \
1571
foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1572
except errors.InvalidRevisionId:
1575
return self._format_properties(
1576
mapping.vcs.show_foreign_revid(foreign_revid))
1578
def _format_properties(self, properties):
1580
for key, value in properties.items():
1581
lines.append(key + ': ' + value)
1375
for key, value in handler(revision).items():
1376
self.to_file.write(indent + key + ': ' + value + '\n')
1584
1378
def show_diff(self, to_file, diff, indent):
1585
1379
for l in diff.rstrip().split('\n'):
1586
1380
to_file.write(indent + '%s\n' % (l,))
1589
# Separator between revisions in long format
1590
_LONG_SEP = '-' * 60
1593
1383
class LongLogFormatter(LogFormatter):
1595
1385
supports_merge_revisions = True
1597
1387
supports_delta = True
1598
1388
supports_tags = True
1599
1389
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
1391
def log_revision(self, revision):
1618
1392
"""Log a revision, either merged or not."""
1619
1393
indent = ' ' * revision.merge_depth
1394
to_file = self.to_file
1395
to_file.write(indent + '-' * 60 + '\n')
1621
1396
if revision.revno is not None:
1622
lines.append('revno: %s%s' % (revision.revno,
1397
to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
1623
1398
self.merge_marker(revision)))
1624
1399
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,))
1400
to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
1628
1401
if self.show_ids:
1402
to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
1629
1404
for parent_id in revision.rev.parent_ids:
1630
lines.append('parent: %s' % (parent_id,))
1631
lines.extend(self.custom_properties(revision.rev))
1405
to_file.write(indent + 'parent: %s\n' % (parent_id,))
1406
self.show_properties(revision.rev, indent)
1633
1408
committer = revision.rev.committer
1634
authors = self.authors(revision.rev, 'all')
1409
authors = revision.rev.get_apparent_authors()
1635
1410
if authors != [committer]:
1636
lines.append('author: %s' % (", ".join(authors),))
1637
lines.append('committer: %s' % (committer,))
1411
to_file.write(indent + 'author: %s\n' % (", ".join(authors),))
1412
to_file.write(indent + 'committer: %s\n' % (committer,))
1639
1414
branch_nick = revision.rev.properties.get('branch-nick', None)
1640
1415
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:')
1416
to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))
1418
date_str = format_date(revision.rev.timestamp,
1419
revision.rev.timezone or 0,
1421
to_file.write(indent + 'timestamp: %s\n' % (date_str,))
1423
to_file.write(indent + 'message:\n')
1649
1424
if not revision.rev.message:
1650
lines.append(' (no message)')
1425
to_file.write(indent + ' (no message)\n')
1652
1427
message = revision.rev.message.rstrip('\r\n')
1653
1428
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)))
1429
to_file.write(indent + ' %s\n' % (l,))
1659
1430
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)
1431
# We don't respect delta_format for compatibility
1432
revision.delta.show(to_file, self.show_ids, indent=indent,
1664
1434
if revision.diff is not None:
1665
1435
to_file.write(indent + 'diff:\n')
1667
1436
# Note: we explicitly don't indent the diff (relative to the
1668
1437
# revision information) so that the output can be fed to patch -p0
1669
1438
self.show_diff(self.to_exact_file, revision.diff, indent)
1670
self.to_exact_file.flush()
1672
1440
def get_advice_separator(self):
1673
1441
"""Get the text separating the log from the closing advice."""
1711
1479
if revision.tags:
1712
1480
tags = ' {%s}' % (', '.join(revision.tags))
1713
1481
to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
1714
revision.revno or "", self.short_author(revision.rev),
1482
revision.revno, self.short_author(revision.rev),
1715
1483
format_date(revision.rev.timestamp,
1716
1484
revision.rev.timezone or 0,
1717
1485
self.show_timezone, date_fmt="%Y-%m-%d",
1718
1486
show_offset=False),
1719
1487
tags, self.merge_marker(revision)))
1720
1488
self.show_properties(revision.rev, indent+offset)
1721
if self.show_ids or revision.revno is None:
1722
1490
to_file.write(indent + offset + 'revision-id:%s\n'
1723
1491
% (revision.rev.revision_id,))
1724
1492
if not revision.rev.message:
1729
1497
to_file.write(indent + offset + '%s\n' % (l,))
1731
1499
if revision.delta is not None:
1732
# Use the standard status output to display changes
1733
from bzrlib.delta import report_delta
1734
report_delta(to_file, revision.delta,
1735
short_status=self.delta_format==1,
1736
show_ids=self.show_ids, indent=indent + offset)
1500
revision.delta.show(to_file, self.show_ids, indent=indent + offset,
1501
short_status=self.delta_format==1)
1737
1502
if revision.diff is not None:
1738
1503
self.show_diff(self.to_exact_file, revision.diff, ' ')
1739
1504
to_file.write('\n')
1778
1539
def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
1779
1540
"""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
1541
:param revno: revision number or None.
1542
Revision numbers counts from 1.
1543
:param rev: revision object
1544
:param max_chars: maximum length of resulting string
1545
:param tags: list of tags or None
1546
:param prefix: string to prefix each line
1547
:return: formatted truncated string
1791
1551
# show revno only when is not None
1792
1552
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))
1553
out.append(self.truncate(self.short_author(rev), 20))
1797
1554
out.append(self.date_string(rev))
1798
1555
if len(rev.parent_ids) > 1:
1799
1556
out.append('[merge]')
1890
1646
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
1649
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1922
1650
# deprecated; for compatibility
1923
1651
lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
2085
1812
:param file_list: the list of paths given on the command line;
2086
1813
the first of these can be a branch location or a file path,
2087
1814
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
1815
:return: (branch, info_list, start_rev_info, end_rev_info) where
2091
1816
info_list is a list of (relative_path, file_id, kind) tuples where
2092
1817
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2093
branch will be read-locked.
2095
from builtins import _get_revision_range
1819
from builtins import _get_revision_range, safe_relpath_files
2096
1820
tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
2097
add_cleanup(b.lock_read().unlock)
2098
1821
# XXX: It's damn messy converting a list of paths to relative paths when
2099
1822
# those paths might be deleted ones, they might be on a case-insensitive
2100
1823
# filesystem and/or they might be in silly locations (like another branch).
2180
1903
properties_handler_registry = registry.Registry()
2182
# Use the properties handlers to print out bug information if available
2183
def _bugs_properties_handler(revision):
2184
if revision.properties.has_key('bugs'):
2185
bug_lines = revision.properties['bugs'].split('\n')
2186
bug_rows = [line.split(' ', 1) for line in bug_lines]
2187
fixed_bug_urls = [row[0] for row in bug_rows if
2188
len(row) > 1 and row[1] == 'fixed']
2191
return {'fixes bug(s)': ' '.join(fixed_bug_urls)}
2194
properties_handler_registry.register('bugs_properties_handler',
2195
_bugs_properties_handler)
1904
properties_handler_registry.register_lazy("foreign",
1906
"show_foreign_properties")
2198
1909
# adapters which revision ids to log are filtered. When log is called, the