187
which_revs = _enumerate_history(branch)
189
if start_revision is None:
192
branch.check_real_revno(start_revision)
194
if end_revision is None:
195
end_revision = len(which_revs)
197
branch.check_real_revno(end_revision)
199
# list indexes are 0-based; revisions are 1-based
200
cut_revs = which_revs[(start_revision-1):(end_revision)]
207
mainline_revs, rev_nos, start_rev_id, end_rev_id = \
208
_get_mainline_revs(branch, start_revision, end_revision)
209
if not mainline_revs:
204
# convert the revision history to a dictionary:
205
rev_nos = dict((k, v) for v, k in cut_revs)
207
# override the mainline to look like the revision history.
208
mainline_revs = [revision_id for index, revision_id in cut_revs]
209
if cut_revs[0][0] == 1:
210
mainline_revs.insert(0, None)
212
mainline_revs.insert(0, which_revs[start_revision-2][1])
213
# how should we show merged revisions ?
214
# old api: show_merge. New api: show_merge_revno
215
show_merge_revno = getattr(lf, 'show_merge_revno', None)
216
show_merge = getattr(lf, 'show_merge', None)
217
if show_merge is None and show_merge_revno is None:
218
# no merged-revno support
219
include_merges = False
221
include_merges = True
222
if show_merge is not None and show_merge_revno is None:
212
if direction == 'reverse':
213
start_rev_id, end_rev_id = end_rev_id, start_rev_id
215
legacy_lf = getattr(lf, 'log_revision', None) is None
217
# pre-0.17 formatters use show for mainline revisions.
218
# how should we show merged revisions ?
219
# pre-0.11 api: show_merge
220
# 0.11-0.16 api: show_merge_revno
221
show_merge_revno = getattr(lf, 'show_merge_revno', None)
222
show_merge = getattr(lf, 'show_merge', None)
223
if show_merge is None and show_merge_revno is None:
224
# no merged-revno support
225
generate_merge_revisions = False
227
generate_merge_revisions = True
223
228
# tell developers to update their code
224
symbol_versioning.warn('LogFormatters should provide show_merge_revno '
225
'instead of show_merge since bzr 0.11.',
229
symbol_versioning.warn('LogFormatters should provide log_revision '
230
'instead of show and show_merge_revno since bzr 0.17.',
226
231
DeprecationWarning, stacklevel=3)
233
generate_merge_revisions = getattr(lf, 'supports_merge_revisions',
227
235
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
228
direction, include_merges=include_merges)
236
direction, include_merges=generate_merge_revisions)
237
view_revisions = _filter_revision_range(list(view_revs_iter),
229
240
if specific_fileid:
230
view_revisions = _get_revisions_touching_file_id(branch,
241
view_revisions = _filter_revisions_touching_file_id(branch,
235
view_revisions = list(view_revs_iter)
237
use_tags = getattr(lf, 'supports_tags', False)
247
generate_tags = getattr(lf, 'supports_tags', False)
240
249
if branch.supports_tags():
241
250
rev_tag_dict = branch.tags.get_reverse_tag_dict()
252
generate_delta = verbose and getattr(lf, 'supports_delta', False)
243
254
def iter_revisions():
244
255
# r = revision, n = revno, d = merge depth
245
256
revision_ids = [r for r, n, d in view_revisions]
246
zeros = set(r for r, n, d in view_revisions if d == 0)
248
258
repository = branch.repository
249
259
while revision_ids:
251
261
revisions = repository.get_revisions(revision_ids[:num])
253
delta_revisions = [r for r in revisions if
254
r.revision_id in zeros]
255
deltas = repository.get_deltas_for_revisions(delta_revisions)
256
cur_deltas = dict(izip((r.revision_id for r in
257
delta_revisions), deltas))
263
deltas = repository.get_deltas_for_revisions(revisions)
264
cur_deltas = dict(izip((r.revision_id for r in revisions),
258
266
for revision in revisions:
259
# The delta value will be None unless
260
# 1. verbose is specified, and
261
# 2. the revision is a mainline revision
262
267
yield revision, cur_deltas.get(revision.revision_id)
263
268
revision_ids = revision_ids[num:]
264
269
num = min(int(num * 1.5), 200)
266
271
# now we just print all the revisions
267
273
for ((rev_id, revno, merge_depth), (rev, delta)) in \
268
274
izip(view_revisions, iter_revisions()):
271
277
if not searchRE.search(rev.message):
276
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
278
lf.show(revno, rev, delta)
280
if show_merge_revno is None:
281
lf.show_merge(rev, merge_depth)
284
lf.show_merge_revno(rev, merge_depth, revno,
285
rev_tag_dict.get(rev_id))
287
lf.show_merge_revno(rev, merge_depth, revno)
290
def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
281
lr = LogRevision(rev, revno, merge_depth, delta,
282
rev_tag_dict.get(rev_id))
285
# support for legacy (pre-0.17) LogFormatters
288
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
290
lf.show(revno, rev, delta)
292
if show_merge_revno is None:
293
lf.show_merge(rev, merge_depth)
296
lf.show_merge_revno(rev, merge_depth, revno,
297
rev_tag_dict.get(rev_id))
299
lf.show_merge_revno(rev, merge_depth, revno)
302
if log_count >= limit:
306
def _get_mainline_revs(branch, start_revision, end_revision):
307
"""Get the mainline revisions from the branch.
309
Generates the list of mainline revisions for the branch.
311
:param branch: The branch containing the revisions.
313
:param start_revision: The first revision to be logged.
314
For backwards compatibility this may be a mainline integer revno,
315
but for merge revision support a RevisionInfo is expected.
317
:param end_revision: The last revision to be logged.
318
For backwards compatibility this may be a mainline integer revno,
319
but for merge revision support a RevisionInfo is expected.
321
:return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
323
which_revs = _enumerate_history(branch)
325
return None, None, None, None
327
# For mainline generation, map start_revision and end_revision to
328
# mainline revnos. If the revision is not on the mainline choose the
329
# appropriate extreme of the mainline instead - the extra will be
331
# Also map the revisions to rev_ids, to be used in the later filtering
334
if start_revision is None:
337
if isinstance(start_revision,RevisionInfo):
338
start_rev_id = start_revision.rev_id
339
start_revno = start_revision.revno or 1
341
branch.check_real_revno(start_revision)
342
start_revno = start_revision
345
if end_revision is None:
346
end_revno = len(which_revs)
348
if isinstance(end_revision,RevisionInfo):
349
end_rev_id = end_revision.rev_id
350
end_revno = end_revision.revno or len(which_revs)
352
branch.check_real_revno(end_revision)
353
end_revno = end_revision
355
if start_revno > end_revno:
356
from bzrlib.errors import BzrCommandError
357
raise BzrCommandError("Start revision must be older than "
360
# list indexes are 0-based; revisions are 1-based
361
cut_revs = which_revs[(start_revno-1):(end_revno)]
363
return None, None, None, None
365
# convert the revision history to a dictionary:
366
rev_nos = dict((k, v) for v, k in cut_revs)
368
# override the mainline to look like the revision history.
369
mainline_revs = [revision_id for index, revision_id in cut_revs]
370
if cut_revs[0][0] == 1:
371
mainline_revs.insert(0, None)
373
mainline_revs.insert(0, which_revs[start_revno-2][1])
374
return mainline_revs, rev_nos, start_rev_id, end_rev_id
377
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
378
"""Filter view_revisions based on revision ranges.
380
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
381
tuples to be filtered.
383
:param start_rev_id: If not NONE specifies the first revision to be logged.
384
If NONE then all revisions up to the end_rev_id are logged.
386
:param end_rev_id: If not NONE specifies the last revision to be logged.
387
If NONE then all revisions up to the end of the log are logged.
389
:return: The filtered view_revisions.
391
if start_rev_id or end_rev_id:
392
revision_ids = [r for r, n, d in view_revisions]
394
start_index = revision_ids.index(start_rev_id)
397
if start_rev_id == end_rev_id:
398
end_index = start_index
401
end_index = revision_ids.index(end_rev_id)
403
end_index = len(view_revisions) - 1
404
# To include the revisions merged into the last revision,
405
# extend end_rev_id down to, but not including, the next rev
406
# with the same or lesser merge_depth
407
end_merge_depth = view_revisions[end_index][2]
409
for index in xrange(end_index+1, len(view_revisions)+1):
410
if view_revisions[index][2] <= end_merge_depth:
411
end_index = index - 1
414
# if the search falls off the end then log to the end as well
415
end_index = len(view_revisions) - 1
416
view_revisions = view_revisions[start_index:end_index+1]
417
return view_revisions
420
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
292
422
"""Return the list of revision ids which touch a given file id.
424
The function filters view_revisions and returns a subset.
294
425
This includes the revisions which directly change the file id,
295
426
and the revisions which merge these changes. So if the
296
427
revision graph is::
532
class LogRevision(object):
533
"""A revision to be logged (by LogFormatter.log_revision).
535
A simple wrapper for the attributes of a revision to be logged.
536
The attributes may or may not be populated, as determined by the
537
logging options and the log formatter capabilities.
540
def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
544
self.merge_depth = merge_depth
401
549
class LogFormatter(object):
402
"""Abstract class to display log messages."""
550
"""Abstract class to display log messages.
552
At a minimum, a derived class must implement the log_revision method.
554
If the LogFormatter needs to be informed of the beginning or end of
555
a log it should implement the begin_log and/or end_log hook methods.
557
A LogFormatter should define the following supports_XXX flags
558
to indicate which LogRevision attributes it supports:
560
- supports_delta must be True if this log formatter supports delta.
561
Otherwise the delta attribute may not be populated.
562
- supports_merge_revisions must be True if this log formatter supports
563
merge revisions. If not, only revisions mainline revisions (those
564
with merge_depth == 0) will be passed to the formatter.
565
- supports_tags must be True if this log formatter supports tags.
566
Otherwise the tags attribute may not be populated.
404
569
def __init__(self, to_file, show_ids=False, show_timezone='original'):
405
570
self.to_file = to_file
406
571
self.show_ids = show_ids
407
572
self.show_timezone = show_timezone
574
# TODO: uncomment this block after show() has been removed.
575
# Until then defining log_revision would prevent _show_log calling show()
576
# in legacy formatters.
577
# def log_revision(self, revision):
580
# :param revision: The LogRevision to be logged.
582
# raise NotImplementedError('not implemented in abstract base')
584
@deprecated_method(zero_seventeen)
409
585
def show(self, revno, rev, delta):
410
586
raise NotImplementedError('not implemented in abstract base')
416
592
class LongLogFormatter(LogFormatter):
418
supports_tags = True # must exist and be True
419
# if this log formatter support tags.
420
# .show() and .show_merge_revno() must then accept
421
# the 'tags'-argument with list of tags
594
supports_merge_revisions = True
595
supports_delta = True
598
@deprecated_method(zero_seventeen)
423
599
def show(self, revno, rev, delta, tags=None):
424
return self._show_helper(revno=revno, rev=rev, delta=delta, tags=tags)
600
lr = LogRevision(rev, revno, 0, delta, tags)
601
return self.log_revision(lr)
426
603
@deprecated_method(zero_eleven)
427
604
def show_merge(self, rev, merge_depth):
428
return self._show_helper(rev=rev, indent=' '*merge_depth,
429
merged=True, delta=None)
605
lr = LogRevision(rev, merge_depth=merge_depth)
606
return self.log_revision(lr)
608
@deprecated_method(zero_seventeen)
431
609
def show_merge_revno(self, rev, merge_depth, revno, tags=None):
432
610
"""Show a merged revision rev, with merge_depth and a revno."""
433
return self._show_helper(rev=rev, revno=revno,
434
indent=' '*merge_depth, merged=True, delta=None, tags=tags)
611
lr = LogRevision(rev, revno, merge_depth, tags=tags)
612
return self.log_revision(lr)
436
def _show_helper(self, rev=None, revno=None, indent='', merged=False,
437
delta=None, tags=None):
438
"""Show a revision, either merged or not."""
614
def log_revision(self, revision):
615
"""Log a revision, either merged or not."""
439
616
from bzrlib.osutils import format_date
617
indent = ' '*revision.merge_depth
440
618
to_file = self.to_file
441
619
print >>to_file, indent+'-' * 60
442
if revno is not None:
443
print >>to_file, indent+'revno:', revno
445
print >>to_file, indent+'tags: %s' % (', '.join(tags))
447
print >>to_file, indent+'merged:', rev.revision_id
449
print >>to_file, indent+'revision-id:', rev.revision_id
620
if revision.revno is not None:
621
print >>to_file, indent+'revno:', revision.revno
623
print >>to_file, indent+'tags: %s' % (', '.join(revision.tags))
450
624
if self.show_ids:
451
for parent_id in rev.parent_ids:
625
print >>to_file, indent+'revision-id:', revision.rev.revision_id
626
for parent_id in revision.rev.parent_ids:
452
627
print >>to_file, indent+'parent:', parent_id
453
print >>to_file, indent+'committer:', rev.committer
628
print >>to_file, indent+'committer:', revision.rev.committer
456
631
print >>to_file, indent+'branch nick: %s' % \
457
rev.properties['branch-nick']
632
revision.rev.properties['branch-nick']
460
date_str = format_date(rev.timestamp,
635
date_str = format_date(revision.rev.timestamp,
636
revision.rev.timezone or 0,
462
637
self.show_timezone)
463
638
print >>to_file, indent+'timestamp: %s' % date_str
465
640
print >>to_file, indent+'message:'
641
if not revision.rev.message:
467
642
print >>to_file, indent+' (no message)'
469
message = rev.message.rstrip('\r\n')
644
message = revision.rev.message.rstrip('\r\n')
470
645
for l in message.split('\n'):
471
646
print >>to_file, indent+' ' + l
472
if delta is not None:
473
delta.show(to_file, self.show_ids)
647
if revision.delta is not None:
648
revision.delta.show(to_file, self.show_ids, indent=indent)
476
651
class ShortLogFormatter(LogFormatter):
653
supports_delta = True
655
@deprecated_method(zero_seventeen)
477
656
def show(self, revno, rev, delta):
657
lr = LogRevision(rev, revno, 0, delta)
658
return self.log_revision(lr)
660
def log_revision(self, revision):
478
661
from bzrlib.osutils import format_date
480
663
to_file = self.to_file
481
date_str = format_date(rev.timestamp, rev.timezone or 0,
483
print >>to_file, "%5s %s\t%s" % (revno, self.short_committer(rev),
484
format_date(rev.timestamp, rev.timezone or 0,
664
date_str = format_date(revision.rev.timestamp,
665
revision.rev.timezone or 0,
668
if len(revision.rev.parent_ids) > 1:
669
is_merge = ' [merge]'
670
print >>to_file, "%5s %s\t%s%s" % (revision.revno,
671
self.short_committer(revision.rev),
672
format_date(revision.rev.timestamp,
673
revision.rev.timezone or 0,
485
674
self.show_timezone, date_fmt="%Y-%m-%d",
487
677
if self.show_ids:
488
print >>to_file, ' revision-id:', rev.revision_id
678
print >>to_file, ' revision-id:', revision.rev.revision_id
679
if not revision.rev.message:
490
680
print >>to_file, ' (no message)'
492
message = rev.message.rstrip('\r\n')
682
message = revision.rev.message.rstrip('\r\n')
493
683
for l in message.split('\n'):
494
684
print >>to_file, ' ' + l
496
686
# TODO: Why not show the modified files in a shorter form as
497
687
# well? rewrap them single lines of appropriate length
498
if delta is not None:
499
delta.show(to_file, self.show_ids)
688
if revision.delta is not None:
689
revision.delta.show(to_file, self.show_ids)
500
690
print >>to_file, ''
503
693
class LineLogFormatter(LogFormatter):
695
def __init__(self, *args, **kwargs):
696
from bzrlib.osutils import terminal_width
697
super(LineLogFormatter, self).__init__(*args, **kwargs)
698
self._max_chars = terminal_width() - 1
504
700
def truncate(self, str, max_len):
505
701
if len(str) <= max_len: