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:
179
which_revs = _enumerate_history(branch)
181
if start_revision is None:
184
branch.check_real_revno(start_revision)
186
if end_revision is None:
187
end_revision = len(which_revs)
189
branch.check_real_revno(end_revision)
191
# list indexes are 0-based; revisions are 1-based
192
cut_revs = which_revs[(start_revision-1):(end_revision)]
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
228
# tell developers to update their code
229
symbol_versioning.warn('LogFormatters should provide log_revision '
230
'instead of show and show_merge_revno since bzr 0.17.',
231
DeprecationWarning, stacklevel=3)
233
generate_merge_revisions = getattr(lf, 'supports_merge_revisions',
235
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
236
direction, include_merges=generate_merge_revisions)
237
view_revisions = _filter_revision_range(list(view_revs_iter),
241
view_revisions = _filter_revisions_touching_file_id(branch,
247
generate_tags = getattr(lf, 'supports_tags', False)
249
if branch.supports_tags():
250
rev_tag_dict = branch.tags.get_reverse_tag_dict()
252
generate_delta = verbose and getattr(lf, 'supports_delta', False)
196
# convert the revision history to a dictionary:
197
rev_nos = dict((k, v) for v, k in cut_revs)
199
# override the mainline to look like the revision history.
200
mainline_revs = [revision_id for index, revision_id in cut_revs]
201
if cut_revs[0][0] == 1:
202
mainline_revs.insert(0, None)
204
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))
254
212
def iter_revisions():
255
213
# r = revision, n = revno, d = merge depth
256
214
revision_ids = [r for r, n, d in view_revisions]
215
zeros = set(r for r, n, d in view_revisions if d == 0)
258
217
repository = branch.repository
259
218
while revision_ids:
261
220
revisions = repository.get_revisions(revision_ids[:num])
263
deltas = repository.get_deltas_for_revisions(revisions)
264
cur_deltas = dict(izip((r.revision_id for r in revisions),
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))
266
227
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
267
231
yield revision, cur_deltas.get(revision.revision_id)
268
232
revision_ids = revision_ids[num:]
269
num = min(int(num * 1.5), 200)
271
235
# now we just print all the revisions
273
236
for ((rev_id, revno, merge_depth), (rev, delta)) in \
274
237
izip(view_revisions, iter_revisions()):
277
240
if not searchRE.search(rev.message):
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,
422
"""Return the list of revision ids which touch a given file id.
424
The function filters view_revisions and returns a subset.
425
This includes the revisions which directly change the file id,
426
and the revisions which merge these changes. So if the
434
And 'C' changes a file, then both C and D will be returned.
436
This will also can be restricted based on a subset of the mainline.
438
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
440
# find all the revisions that change the specific file
441
file_weave = branch.repository.weave_store.get_weave(file_id,
442
branch.repository.get_transaction())
443
weave_modifed_revisions = set(file_weave.versions())
444
# build the ancestry of each revision in the graph
445
# - only listing the ancestors that change the specific file.
446
rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
447
sorted_rev_list = topo_sort(rev_graph)
449
for rev in sorted_rev_list:
450
parents = rev_graph[rev]
451
if rev not in weave_modifed_revisions and len(parents) == 1:
452
# We will not be adding anything new, so just use a reference to
453
# the parent ancestry.
454
rev_ancestry = ancestry[parents[0]]
457
if rev in weave_modifed_revisions:
458
rev_ancestry.add(rev)
459
for parent in parents:
460
rev_ancestry = rev_ancestry.union(ancestry[parent])
461
ancestry[rev] = rev_ancestry
463
def is_merging_rev(r):
464
parents = rev_graph[r]
466
leftparent = parents[0]
467
for rightparent in parents[1:]:
468
if not ancestry[leftparent].issuperset(
469
ancestry[rightparent]):
473
# filter from the view the revisions that did not change or merge
475
return [(r, n, d) for r, n, d in view_revs_iter
476
if r in weave_modifed_revisions or is_merging_rev(r)]
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)
479
259
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
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
549
313
class LogFormatter(object):
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.
314
"""Abstract class to display log messages."""
569
316
def __init__(self, to_file, show_ids=False, show_timezone='original'):
570
317
self.to_file = to_file
571
318
self.show_ids = show_ids
572
319
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)
585
321
def show(self, revno, rev, delta):
586
322
raise NotImplementedError('not implemented in abstract base')
588
324
def short_committer(self, rev):
589
325
return re.sub('<.*@.*>', '', rev.committer).strip(' ')
592
328
class LongLogFormatter(LogFormatter):
594
supports_merge_revisions = True
595
supports_delta = True
598
@deprecated_method(zero_seventeen)
599
def show(self, revno, rev, delta, tags=None):
600
lr = LogRevision(rev, revno, 0, delta, tags)
601
return self.log_revision(lr)
603
@deprecated_method(zero_eleven)
329
def show(self, revno, rev, delta):
330
return self._show_helper(revno=revno, rev=rev, delta=delta)
604
332
def show_merge(self, rev, merge_depth):
605
lr = LogRevision(rev, merge_depth=merge_depth)
606
return self.log_revision(lr)
608
@deprecated_method(zero_seventeen)
609
def show_merge_revno(self, rev, merge_depth, revno, tags=None):
610
"""Show a merged revision rev, with merge_depth and a revno."""
611
lr = LogRevision(rev, revno, merge_depth, tags=tags)
612
return self.log_revision(lr)
614
def log_revision(self, revision):
615
"""Log a revision, either merged or not."""
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."""
616
337
from bzrlib.osutils import format_date
617
indent = ' '*revision.merge_depth
618
338
to_file = self.to_file
619
339
print >>to_file, indent+'-' * 60
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))
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
624
346
if self.show_ids:
625
print >>to_file, indent+'revision-id:', revision.rev.revision_id
626
for parent_id in revision.rev.parent_ids:
347
for parent_id in rev.parent_ids:
627
348
print >>to_file, indent+'parent:', parent_id
628
print >>to_file, indent+'committer:', revision.rev.committer
349
print >>to_file, indent+'committer:', rev.committer
631
351
print >>to_file, indent+'branch nick: %s' % \
632
revision.rev.properties['branch-nick']
352
rev.properties['branch-nick']
635
date_str = format_date(revision.rev.timestamp,
636
revision.rev.timezone or 0,
355
date_str = format_date(rev.timestamp,
637
357
self.show_timezone)
638
358
print >>to_file, indent+'timestamp: %s' % date_str
640
360
print >>to_file, indent+'message:'
641
if not revision.rev.message:
642
362
print >>to_file, indent+' (no message)'
644
message = revision.rev.message.rstrip('\r\n')
364
message = rev.message.rstrip('\r\n')
645
365
for l in message.split('\n'):
646
366
print >>to_file, indent+' ' + l
647
if revision.delta is not None:
648
revision.delta.show(to_file, self.show_ids, indent=indent)
368
delta.show(to_file, self.show_ids)
651
371
class ShortLogFormatter(LogFormatter):
653
supports_delta = True
655
@deprecated_method(zero_seventeen)
656
372
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):
661
373
from bzrlib.osutils import format_date
663
375
to_file = self.to_file
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,
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,
674
380
self.show_timezone, date_fmt="%Y-%m-%d",
677
382
if self.show_ids:
678
print >>to_file, ' revision-id:', revision.rev.revision_id
679
if not revision.rev.message:
383
print >>to_file, ' revision-id:', rev.revision_id
680
385
print >>to_file, ' (no message)'
682
message = revision.rev.message.rstrip('\r\n')
387
message = rev.message.rstrip('\r\n')
683
388
for l in message.split('\n'):
684
389
print >>to_file, ' ' + l
686
391
# TODO: Why not show the modified files in a shorter form as
687
392
# well? rewrap them single lines of appropriate length
688
if revision.delta is not None:
689
revision.delta.show(to_file, self.show_ids)
394
delta.show(to_file, self.show_ids)
690
395
print >>to_file, ''
693
398
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
700
399
def truncate(self, str, max_len):
701
400
if len(str) <= max_len:
745
439
lf = LineLogFormatter(None)
746
440
return lf.log_string(None, rev, max_chars)
749
class LogFormatterRegistry(registry.Registry):
750
"""Registry for log formatters"""
752
def make_formatter(self, name, *args, **kwargs):
753
"""Construct a formatter from arguments.
755
:param name: Name of the formatter to construct. 'short', 'long' and
758
return self.get(name)(*args, **kwargs)
760
def get_default(self, branch):
761
return self.get(branch.get_config().log_format())
764
log_formatter_registry = LogFormatterRegistry()
767
log_formatter_registry.register('short', ShortLogFormatter,
768
'Moderately short log format')
769
log_formatter_registry.register('long', LongLogFormatter,
770
'Detailed log format')
771
log_formatter_registry.register('line', LineLogFormatter,
772
'Log format with one line per revision')
443
'long': LongLogFormatter,
444
'short': ShortLogFormatter,
445
'line': LineLogFormatter,
775
448
def register_formatter(name, formatter):
776
log_formatter_registry.register(name, formatter)
449
FORMATTERS[name] = formatter
779
451
def log_formatter(name, *args, **kwargs):
780
452
"""Construct a formatter from arguments.