348
348
rqst['delta_type'] = None
349
349
if not getattr(lf, 'supports_diff', False):
350
350
rqst['diff_type'] = None
351
if not getattr(lf, 'supports_merge_revisions', False):
352
rqst['_allow_single_merge_revision'] = getattr(lf,
353
'supports_single_merge_revision', False)
355
352
# Find and print the interesting revisions
356
353
generator = self._generator_factory(self.branch, rqst)
357
354
for lr in generator.iter_log_revisions():
358
355
lf.log_revision(lr)
360
358
def _generator_factory(self, branch, rqst):
361
359
"""Make the LogGenerator object to use.
453
451
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
454
452
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
455
453
self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
456
rqst.get('_allow_single_merge_revision'),
457
454
delayed_graph_generation=delayed_graph_generation)
459
456
# Apply the other filters
468
465
# filter_revisions_touching_file_id() requires them ...
470
467
view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
471
self.end_rev_id, rqst.get('direction'), True,
472
rqst.get('_allow_single_merge_revision'))
468
self.end_rev_id, rqst.get('direction'), True)
473
469
if not isinstance(view_revisions, list):
474
470
view_revisions = list(view_revisions)
475
471
view_revisions = _filter_revisions_touching_file_id(self.branch,
482
478
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
483
generate_merge_revisions, allow_single_merge_revision,
484
delayed_graph_generation=False):
479
generate_merge_revisions, delayed_graph_generation=False):
485
480
"""Calculate the revisions to view.
487
482
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
495
490
generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
496
491
(not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
497
492
if generate_single_revision:
498
return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno,
499
allow_single_merge_revision)
493
return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
501
495
# If we only want to see linear revisions, we can iterate ...
502
496
if not generate_merge_revisions:
507
501
direction, delayed_graph_generation)
510
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno,
511
allow_single_merge_revision):
504
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
512
505
if rev_id == br_rev_id:
514
507
return [(br_rev_id, br_revno, 0)]
516
509
revno = branch.revision_id_to_dotted_revno(rev_id)
517
if len(revno) > 1 and not allow_single_merge_revision:
518
# It's a merge revision and the log formatter is
519
# completely brain dead. This "feature" of allowing
520
# log formatters incapable of displaying dotted revnos
521
# ought to be deprecated IMNSHO. IGC 20091022
522
raise errors.BzrCommandError('Selected log formatter only'
523
' supports mainline revisions.')
524
510
revno_str = '.'.join(str(n) for n in revno)
525
511
return [(rev_id, revno_str, 0)]
684
670
def calculate_view_revisions(branch, start_revision, end_revision, direction,
685
specific_fileid, generate_merge_revisions, allow_single_merge_revision):
671
specific_fileid, generate_merge_revisions):
686
672
"""Calculate the revisions to view.
688
674
:return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
694
680
start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
696
682
view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
697
direction, generate_merge_revisions or specific_fileid,
698
allow_single_merge_revision))
683
direction, generate_merge_revisions or specific_fileid))
699
684
if specific_fileid:
700
685
view_revisions = _filter_revisions_touching_file_id(branch,
701
686
specific_fileid, view_revisions,
1283
1268
one (2) should be used.
1285
1270
- supports_merge_revisions must be True if this log formatter supports
1286
merge revisions. If not, and if supports_single_merge_revision is
1287
also not True, then only mainline revisions will be passed to the
1271
merge revisions. If not, then only mainline revisions will be passed
1290
1274
- preferred_levels is the number of levels this formatter defaults to.
1291
1275
The default value is zero meaning display all levels.
1292
1276
This value is only relevant if supports_merge_revisions is True.
1294
- supports_single_merge_revision must be True if this log formatter
1295
supports logging only a single merge revision. This flag is
1296
only relevant if supports_merge_revisions is not True.
1298
1278
- supports_tags must be True if this log formatter supports tags.
1299
1279
Otherwise the tags attribute may not be populated.
1311
1291
preferred_levels = 0
1313
1293
def __init__(self, to_file, show_ids=False, show_timezone='original',
1314
delta_format=None, levels=None):
1294
delta_format=None, levels=None, show_advice=False):
1315
1295
"""Create a LogFormatter.
1317
1297
:param to_file: the file to output to
1318
1298
:param show_ids: if True, revision-ids are to be displayed
1319
1299
:param show_timezone: the timezone to use
1320
1300
:param delta_format: the level of delta information to display
1321
or None to leave it u to the formatter to decide
1301
or None to leave it to the formatter to decide
1322
1302
:param levels: the number of levels to display; None or -1 to
1323
1303
let the log formatter decide.
1304
:param show_advice: whether to show advice at the end of the
1325
1307
self.to_file = to_file
1326
1308
# 'exact' stream used to show diff, it should print content 'as is'
1333
1315
delta_format = 2 # long format
1334
1316
self.delta_format = delta_format
1335
1317
self.levels = levels
1318
self._show_advice = show_advice
1319
self._merge_count = 0
1337
1321
def get_levels(self):
1338
1322
"""Get the number of levels to display or 0 for all."""
1339
1323
if getattr(self, 'supports_merge_revisions', False):
1340
1324
if self.levels is None or self.levels == -1:
1341
return self.preferred_levels
1325
self.levels = self.preferred_levels
1346
1330
def log_revision(self, revision):
1347
1331
"""Log a revision.
1351
1335
raise NotImplementedError('not implemented in abstract base')
1337
def show_advice(self):
1338
"""Output user advice, if any, when the log is completed."""
1339
if self._show_advice and self.levels == 1 and self._merge_count > 0:
1340
advice_sep = self.get_advice_separator()
1342
self.to_file.write(advice_sep)
1344
"Use --include-merges or -n0 to see merged revisions.\n")
1346
def get_advice_separator(self):
1347
"""Get the text separating the log from the closing advice."""
1353
1350
def short_committer(self, rev):
1354
1351
name, address = config.parse_username(rev.committer)
1362
def merge_marker(self, revision):
1363
"""Get the merge marker to include in the output or '' if none."""
1364
if len(revision.rev.parent_ids) > 1:
1365
self._merge_count += 1
1370
def show_foreign_info(self, rev, indent):
1371
"""Custom log displayer for foreign revision identifiers.
1373
:param rev: Revision object.
1375
# Revision comes directly from a foreign repository
1376
if isinstance(rev, foreign.ForeignRevision):
1377
self._write_properties(indent, rev.mapping.vcs.show_foreign_revid(
1381
# Imported foreign revision revision ids always contain :
1382
if not ":" in rev.revision_id:
1385
# Revision was once imported from a foreign repository
1387
foreign_revid, mapping = \
1388
foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
1389
except errors.InvalidRevisionId:
1392
self._write_properties(indent,
1393
mapping.vcs.show_foreign_revid(foreign_revid))
1365
1395
def show_properties(self, revision, indent):
1366
1396
"""Displays the custom properties returned by each registered handler.
1368
1398
If a registered handler raises an error it is propagated.
1370
1400
for key, handler in properties_handler_registry.iteritems():
1371
for key, value in handler(revision).items():
1372
self.to_file.write(indent + key + ': ' + value + '\n')
1401
self._write_properties(indent, handler(revision))
1403
def _write_properties(self, indent, properties):
1404
for key, value in properties.items():
1405
self.to_file.write(indent + key + ': ' + value + '\n')
1374
1407
def show_diff(self, to_file, diff, indent):
1375
1408
for l in diff.rstrip().split('\n'):
1389
1423
to_file = self.to_file
1390
1424
to_file.write(indent + '-' * 60 + '\n')
1391
1425
if revision.revno is not None:
1392
to_file.write(indent + 'revno: %s\n' % (revision.revno,))
1426
to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
1427
self.merge_marker(revision)))
1393
1428
if revision.tags:
1394
1429
to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
1395
1430
if self.show_ids:
1397
1432
to_file.write('\n')
1398
1433
for parent_id in revision.rev.parent_ids:
1399
1434
to_file.write(indent + 'parent: %s\n' % (parent_id,))
1435
self.show_foreign_info(revision.rev, indent)
1400
1436
self.show_properties(revision.rev, indent)
1402
1438
committer = revision.rev.committer
1431
1467
# revision information) so that the output can be fed to patch -p0
1432
1468
self.show_diff(self.to_exact_file, revision.diff, indent)
1470
def get_advice_separator(self):
1471
"""Get the text separating the log from the closing advice."""
1472
return '-' * 60 + '\n'
1435
1475
class ShortLogFormatter(LogFormatter):
1477
1514
revision.rev.timezone or 0,
1478
1515
self.show_timezone, date_fmt="%Y-%m-%d",
1479
1516
show_offset=False),
1517
tags, self.merge_marker(revision)))
1518
self.show_foreign_info(revision.rev, indent+offset)
1481
1519
self.show_properties(revision.rev, indent+offset)
1482
1520
if self.show_ids:
1483
1521
to_file.write(indent + offset + 'revision-id:%s\n'
1827
1865
start_rev_info, end_rev_info = _get_revision_range(revisionspec_list, b,
1867
if relpaths in ([], [u'']):
1868
return b, [], start_rev_info, end_rev_info
1829
1869
if start_rev_info is None and end_rev_info is None:
1830
1870
if tree is None:
1831
1871
tree = b.basis_tree()
1894
1934
properties_handler_registry = registry.Registry()
1895
properties_handler_registry.register_lazy("foreign",
1897
"show_foreign_properties")
1900
1937
# adapters which revision ids to log are filtered. When log is called, the