202
228
mainline_revs.insert(0, None)
204
230
mainline_revs.insert(0, which_revs[start_revision-2][1])
205
if getattr(lf, 'show_merge', None) is not None:
206
include_merges = True
208
include_merges = False
209
view_revisions = list(get_view_revisions(mainline_revs, rev_nos, branch,
210
direction, include_merges=include_merges))
231
legacy_lf = getattr(lf, 'log_revision', None) is None
233
# pre-0.17 formatters use show for mainline revisions.
234
# how should we show merged revisions ?
235
# pre-0.11 api: show_merge
236
# 0.11-0.16 api: show_merge_revno
237
show_merge_revno = getattr(lf, 'show_merge_revno', None)
238
show_merge = getattr(lf, 'show_merge', None)
239
if show_merge is None and show_merge_revno is None:
240
# no merged-revno support
241
generate_merge_revisions = False
243
generate_merge_revisions = True
244
# tell developers to update their code
245
symbol_versioning.warn('LogFormatters should provide log_revision '
246
'instead of show and show_merge_revno since bzr 0.17.',
247
DeprecationWarning, stacklevel=3)
249
generate_merge_revisions = getattr(lf, 'supports_merge_revisions',
251
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
252
direction, include_merges=generate_merge_revisions)
254
view_revisions = _get_revisions_touching_file_id(branch,
259
view_revisions = list(view_revs_iter)
262
generate_tags = getattr(lf, 'supports_tags', False)
264
if branch.supports_tags():
265
rev_tag_dict = branch.tags.get_reverse_tag_dict()
267
generate_delta = verbose and getattr(lf, 'supports_delta', False)
212
269
def iter_revisions():
213
270
# r = revision, n = revno, d = merge depth
214
271
revision_ids = [r for r, n, d in view_revisions]
215
zeros = set(r for r, n, d in view_revisions if d == 0)
217
273
repository = branch.repository
218
274
while revision_ids:
220
276
revisions = repository.get_revisions(revision_ids[:num])
221
if verbose or specific_fileid:
222
delta_revisions = [r for r in revisions if
223
r.revision_id in zeros]
224
deltas = repository.get_deltas_for_revisions(delta_revisions)
225
cur_deltas = dict(izip((r.revision_id for r in
226
delta_revisions), deltas))
278
deltas = repository.get_deltas_for_revisions(revisions)
279
cur_deltas = dict(izip((r.revision_id for r in revisions),
227
281
for revision in revisions:
228
# The delta value will be None unless
229
# 1. verbose or specific_fileid is specified, and
230
# 2. the revision is a mainline revision
231
282
yield revision, cur_deltas.get(revision.revision_id)
232
283
revision_ids = revision_ids[num:]
284
num = min(int(num * 1.5), 200)
235
286
# now we just print all the revisions
236
288
for ((rev_id, revno, merge_depth), (rev, delta)) in \
237
289
izip(view_revisions, iter_revisions()):
240
292
if not searchRE.search(rev.message):
244
# a mainline revision.
247
if not delta.touches_file_id(specific_fileid):
251
# although we calculated it, throw it away without display
254
lf.show(revno, rev, delta)
256
lf.show_merge(rev, merge_depth)
296
lr = LogRevision(rev, revno, merge_depth, delta,
297
rev_tag_dict.get(rev_id))
300
# support for legacy (pre-0.17) LogFormatters
303
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
305
lf.show(revno, rev, delta)
307
if show_merge_revno is None:
308
lf.show_merge(rev, merge_depth)
311
lf.show_merge_revno(rev, merge_depth, revno,
312
rev_tag_dict.get(rev_id))
314
lf.show_merge_revno(rev, merge_depth, revno)
317
if log_count >= limit:
321
def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
323
"""Return the list of revision ids which touch a given file id.
325
This includes the revisions which directly change the file id,
326
and the revisions which merge these changes. So if the
334
And 'C' changes a file, then both C and D will be returned.
336
This will also can be restricted based on a subset of the mainline.
338
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
340
# find all the revisions that change the specific file
341
file_weave = branch.repository.weave_store.get_weave(file_id,
342
branch.repository.get_transaction())
343
weave_modifed_revisions = set(file_weave.versions())
344
# build the ancestry of each revision in the graph
345
# - only listing the ancestors that change the specific file.
346
rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
347
sorted_rev_list = topo_sort(rev_graph)
349
for rev in sorted_rev_list:
350
parents = rev_graph[rev]
351
if rev not in weave_modifed_revisions and len(parents) == 1:
352
# We will not be adding anything new, so just use a reference to
353
# the parent ancestry.
354
rev_ancestry = ancestry[parents[0]]
357
if rev in weave_modifed_revisions:
358
rev_ancestry.add(rev)
359
for parent in parents:
360
rev_ancestry = rev_ancestry.union(ancestry[parent])
361
ancestry[rev] = rev_ancestry
363
def is_merging_rev(r):
364
parents = rev_graph[r]
366
leftparent = parents[0]
367
for rightparent in parents[1:]:
368
if not ancestry[leftparent].issuperset(
369
ancestry[rightparent]):
373
# filter from the view the revisions that did not change or merge
375
return [(r, n, d) for r, n, d in view_revs_iter
376
if r in weave_modifed_revisions or is_merging_rev(r)]
259
379
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
432
class LogRevision(object):
433
"""A revision to be logged (by LogFormatter.log_revision).
435
A simple wrapper for the attributes of a revision to be logged.
436
The attributes may or may not be populated, as determined by the
437
logging options and the log formatter capabilities.
440
def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
444
self.merge_depth = merge_depth
313
449
class LogFormatter(object):
314
"""Abstract class to display log messages."""
450
"""Abstract class to display log messages.
452
At a minimum, a derived class must implement the log_revision method.
454
If the LogFormatter needs to be informed of the beginning or end of
455
a log it should implement the begin_log and/or end_log hook methods.
457
A LogFormatter should define the following supports_XXX flags
458
to indicate which LogRevision attributes it supports:
460
- supports_delta must be True if this log formatter supports delta.
461
Otherwise the delta attribute may not be populated.
462
- supports_merge_revisions must be True if this log formatter supports
463
merge revisions. If not, only revisions mainline revisions (those
464
with merge_depth == 0) will be passed to the formatter.
465
- supports_tags must be True if this log formatter supports tags.
466
Otherwise the tags attribute may not be populated.
316
469
def __init__(self, to_file, show_ids=False, show_timezone='original'):
317
470
self.to_file = to_file
318
471
self.show_ids = show_ids
319
472
self.show_timezone = show_timezone
474
# TODO: uncomment this block after show() has been removed.
475
# Until then defining log_revision would prevent _show_log calling show()
476
# in legacy formatters.
477
# def log_revision(self, revision):
480
# :param revision: The LogRevision to be logged.
482
# raise NotImplementedError('not implemented in abstract base')
484
@deprecated_method(zero_seventeen)
321
485
def show(self, revno, rev, delta):
322
486
raise NotImplementedError('not implemented in abstract base')
324
488
def short_committer(self, rev):
325
489
return re.sub('<.*@.*>', '', rev.committer).strip(' ')
328
492
class LongLogFormatter(LogFormatter):
329
def show(self, revno, rev, delta):
330
return self._show_helper(revno=revno, rev=rev, delta=delta)
494
supports_merge_revisions = True
495
supports_delta = True
498
@deprecated_method(zero_seventeen)
499
def show(self, revno, rev, delta, tags=None):
500
lr = LogRevision(rev, revno, 0, delta, tags)
501
return self.log_revision(lr)
503
@deprecated_method(zero_eleven)
332
504
def show_merge(self, rev, merge_depth):
333
return self._show_helper(rev=rev, indent=' '*merge_depth, merged=True, delta=None)
335
def _show_helper(self, rev=None, revno=None, indent='', merged=False, delta=None):
336
"""Show a revision, either merged or not."""
505
lr = LogRevision(rev, merge_depth=merge_depth)
506
return self.log_revision(lr)
508
@deprecated_method(zero_seventeen)
509
def show_merge_revno(self, rev, merge_depth, revno, tags=None):
510
"""Show a merged revision rev, with merge_depth and a revno."""
511
lr = LogRevision(rev, revno, merge_depth, tags=tags)
512
return self.log_revision(lr)
514
def log_revision(self, revision):
515
"""Log a revision, either merged or not."""
337
516
from bzrlib.osutils import format_date
517
indent = ' '*revision.merge_depth
338
518
to_file = self.to_file
339
519
print >>to_file, indent+'-' * 60
340
if revno is not None:
341
print >>to_file, 'revno:', revno
343
print >>to_file, indent+'merged:', rev.revision_id
345
print >>to_file, indent+'revision-id:', rev.revision_id
520
if revision.revno is not None:
521
print >>to_file, indent+'revno:', revision.revno
523
print >>to_file, indent+'tags: %s' % (', '.join(revision.tags))
346
524
if self.show_ids:
347
for parent_id in rev.parent_ids:
525
print >>to_file, indent+'revision-id:', revision.rev.revision_id
526
for parent_id in revision.rev.parent_ids:
348
527
print >>to_file, indent+'parent:', parent_id
349
print >>to_file, indent+'committer:', rev.committer
528
print >>to_file, indent+'committer:', revision.rev.committer
351
531
print >>to_file, indent+'branch nick: %s' % \
352
rev.properties['branch-nick']
532
revision.rev.properties['branch-nick']
355
date_str = format_date(rev.timestamp,
535
date_str = format_date(revision.rev.timestamp,
536
revision.rev.timezone or 0,
357
537
self.show_timezone)
358
538
print >>to_file, indent+'timestamp: %s' % date_str
360
540
print >>to_file, indent+'message:'
541
if not revision.rev.message:
362
542
print >>to_file, indent+' (no message)'
364
message = rev.message.rstrip('\r\n')
544
message = revision.rev.message.rstrip('\r\n')
365
545
for l in message.split('\n'):
366
546
print >>to_file, indent+' ' + l
367
if delta is not None:
368
delta.show(to_file, self.show_ids)
547
if revision.delta is not None:
548
revision.delta.show(to_file, self.show_ids, indent=indent)
371
551
class ShortLogFormatter(LogFormatter):
553
supports_delta = True
555
@deprecated_method(zero_seventeen)
372
556
def show(self, revno, rev, delta):
557
lr = LogRevision(rev, revno, 0, delta)
558
return self.log_revision(lr)
560
def log_revision(self, revision):
373
561
from bzrlib.osutils import format_date
375
563
to_file = self.to_file
376
date_str = format_date(rev.timestamp, rev.timezone or 0,
378
print >>to_file, "%5d %s\t%s" % (revno, self.short_committer(rev),
379
format_date(rev.timestamp, rev.timezone or 0,
564
date_str = format_date(revision.rev.timestamp,
565
revision.rev.timezone or 0,
568
if len(revision.rev.parent_ids) > 1:
569
is_merge = ' [merge]'
570
print >>to_file, "%5s %s\t%s%s" % (revision.revno,
571
self.short_committer(revision.rev),
572
format_date(revision.rev.timestamp,
573
revision.rev.timezone or 0,
380
574
self.show_timezone, date_fmt="%Y-%m-%d",
382
577
if self.show_ids:
383
print >>to_file, ' revision-id:', rev.revision_id
578
print >>to_file, ' revision-id:', revision.rev.revision_id
579
if not revision.rev.message:
385
580
print >>to_file, ' (no message)'
387
message = rev.message.rstrip('\r\n')
582
message = revision.rev.message.rstrip('\r\n')
388
583
for l in message.split('\n'):
389
584
print >>to_file, ' ' + l
391
586
# TODO: Why not show the modified files in a shorter form as
392
587
# well? rewrap them single lines of appropriate length
393
if delta is not None:
394
delta.show(to_file, self.show_ids)
588
if revision.delta is not None:
589
revision.delta.show(to_file, self.show_ids)
395
590
print >>to_file, ''
398
593
class LineLogFormatter(LogFormatter):
595
def __init__(self, *args, **kwargs):
596
from bzrlib.osutils import terminal_width
597
super(LineLogFormatter, self).__init__(*args, **kwargs)
598
self._max_chars = terminal_width() - 1
399
600
def truncate(self, str, max_len):
400
601
if len(str) <= max_len:
439
645
lf = LineLogFormatter(None)
440
646
return lf.log_string(None, rev, max_chars)
443
'long': LongLogFormatter,
444
'short': ShortLogFormatter,
445
'line': LineLogFormatter,
649
class LogFormatterRegistry(registry.Registry):
650
"""Registry for log formatters"""
652
def make_formatter(self, name, *args, **kwargs):
653
"""Construct a formatter from arguments.
655
:param name: Name of the formatter to construct. 'short', 'long' and
658
return self.get(name)(*args, **kwargs)
660
def get_default(self, branch):
661
return self.get(branch.get_config().log_format())
664
log_formatter_registry = LogFormatterRegistry()
667
log_formatter_registry.register('short', ShortLogFormatter,
668
'Moderately short log format')
669
log_formatter_registry.register('long', LongLogFormatter,
670
'Detailed log format')
671
log_formatter_registry.register('line', LineLogFormatter,
672
'Log format with one line per revision')
448
675
def register_formatter(name, formatter):
449
FORMATTERS[name] = formatter
676
log_formatter_registry.register(name, formatter)
451
679
def log_formatter(name, *args, **kwargs):
452
680
"""Construct a formatter from arguments.