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,
246
# rebase merge_depth - unless there are no revisions or
247
# either the first or last revision have merge_depth = 0.
248
if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
249
min_depth = min([d for r,n,d in view_revisions])
251
view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
254
generate_tags = getattr(lf, 'supports_tags', False)
256
if branch.supports_tags():
257
rev_tag_dict = branch.tags.get_reverse_tag_dict()
259
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))
261
212
def iter_revisions():
262
213
# r = revision, n = revno, d = merge depth
263
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)
265
217
repository = branch.repository
266
218
while revision_ids:
268
220
revisions = repository.get_revisions(revision_ids[:num])
270
deltas = repository.get_deltas_for_revisions(revisions)
271
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))
273
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
274
231
yield revision, cur_deltas.get(revision.revision_id)
275
232
revision_ids = revision_ids[num:]
276
num = min(int(num * 1.5), 200)
278
235
# now we just print all the revisions
280
236
for ((rev_id, revno, merge_depth), (rev, delta)) in \
281
237
izip(view_revisions, iter_revisions()):
284
240
if not searchRE.search(rev.message):
288
lr = LogRevision(rev, revno, merge_depth, delta,
289
rev_tag_dict.get(rev_id))
292
# support for legacy (pre-0.17) LogFormatters
295
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
297
lf.show(revno, rev, delta)
299
if show_merge_revno is None:
300
lf.show_merge(rev, merge_depth)
303
lf.show_merge_revno(rev, merge_depth, revno,
304
rev_tag_dict.get(rev_id))
306
lf.show_merge_revno(rev, merge_depth, revno)
309
if log_count >= limit:
313
def _get_mainline_revs(branch, start_revision, end_revision):
314
"""Get the mainline revisions from the branch.
316
Generates the list of mainline revisions for the branch.
318
:param branch: The branch containing the revisions.
320
:param start_revision: The first revision to be logged.
321
For backwards compatibility this may be a mainline integer revno,
322
but for merge revision support a RevisionInfo is expected.
324
:param end_revision: The last revision to be logged.
325
For backwards compatibility this may be a mainline integer revno,
326
but for merge revision support a RevisionInfo is expected.
328
:return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
330
which_revs = _enumerate_history(branch)
332
return None, None, None, None
334
# For mainline generation, map start_revision and end_revision to
335
# mainline revnos. If the revision is not on the mainline choose the
336
# appropriate extreme of the mainline instead - the extra will be
338
# Also map the revisions to rev_ids, to be used in the later filtering
341
if start_revision is None:
344
if isinstance(start_revision,RevisionInfo):
345
start_rev_id = start_revision.rev_id
346
start_revno = start_revision.revno or 1
348
branch.check_real_revno(start_revision)
349
start_revno = start_revision
352
if end_revision is None:
353
end_revno = len(which_revs)
355
if isinstance(end_revision,RevisionInfo):
356
end_rev_id = end_revision.rev_id
357
end_revno = end_revision.revno or len(which_revs)
359
branch.check_real_revno(end_revision)
360
end_revno = end_revision
362
if start_revno > end_revno:
363
from bzrlib.errors import BzrCommandError
364
raise BzrCommandError("Start revision must be older than "
367
# list indexes are 0-based; revisions are 1-based
368
cut_revs = which_revs[(start_revno-1):(end_revno)]
370
return None, None, None, None
372
# convert the revision history to a dictionary:
373
rev_nos = dict((k, v) for v, k in cut_revs)
375
# override the mainline to look like the revision history.
376
mainline_revs = [revision_id for index, revision_id in cut_revs]
377
if cut_revs[0][0] == 1:
378
mainline_revs.insert(0, None)
380
mainline_revs.insert(0, which_revs[start_revno-2][1])
381
return mainline_revs, rev_nos, start_rev_id, end_rev_id
384
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
385
"""Filter view_revisions based on revision ranges.
387
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
388
tuples to be filtered.
390
:param start_rev_id: If not NONE specifies the first revision to be logged.
391
If NONE then all revisions up to the end_rev_id are logged.
393
:param end_rev_id: If not NONE specifies the last revision to be logged.
394
If NONE then all revisions up to the end of the log are logged.
396
:return: The filtered view_revisions.
398
if start_rev_id or end_rev_id:
399
revision_ids = [r for r, n, d in view_revisions]
401
start_index = revision_ids.index(start_rev_id)
404
if start_rev_id == end_rev_id:
405
end_index = start_index
408
end_index = revision_ids.index(end_rev_id)
410
end_index = len(view_revisions) - 1
411
# To include the revisions merged into the last revision,
412
# extend end_rev_id down to, but not including, the next rev
413
# with the same or lesser merge_depth
414
end_merge_depth = view_revisions[end_index][2]
416
for index in xrange(end_index+1, len(view_revisions)+1):
417
if view_revisions[index][2] <= end_merge_depth:
418
end_index = index - 1
421
# if the search falls off the end then log to the end as well
422
end_index = len(view_revisions) - 1
423
view_revisions = view_revisions[start_index:end_index+1]
424
return view_revisions
427
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
429
"""Return the list of revision ids which touch a given file id.
431
The function filters view_revisions and returns a subset.
432
This includes the revisions which directly change the file id,
433
and the revisions which merge these changes. So if the
441
And 'C' changes a file, then both C and D will be returned.
443
This will also can be restricted based on a subset of the mainline.
445
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
447
# find all the revisions that change the specific file
448
file_weave = branch.repository.weave_store.get_weave(file_id,
449
branch.repository.get_transaction())
450
weave_modifed_revisions = set(file_weave.versions())
451
# build the ancestry of each revision in the graph
452
# - only listing the ancestors that change the specific file.
453
rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
454
sorted_rev_list = topo_sort(rev_graph)
456
for rev in sorted_rev_list:
457
parents = rev_graph[rev]
458
if rev not in weave_modifed_revisions and len(parents) == 1:
459
# We will not be adding anything new, so just use a reference to
460
# the parent ancestry.
461
rev_ancestry = ancestry[parents[0]]
464
if rev in weave_modifed_revisions:
465
rev_ancestry.add(rev)
466
for parent in parents:
467
rev_ancestry = rev_ancestry.union(ancestry[parent])
468
ancestry[rev] = rev_ancestry
470
def is_merging_rev(r):
471
parents = rev_graph[r]
473
leftparent = parents[0]
474
for rightparent in parents[1:]:
475
if not ancestry[leftparent].issuperset(
476
ancestry[rightparent]):
480
# filter from the view the revisions that did not change or merge
482
return [(r, n, d) for r, n, d in view_revs_iter
483
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)
486
259
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
539
class LogRevision(object):
540
"""A revision to be logged (by LogFormatter.log_revision).
542
A simple wrapper for the attributes of a revision to be logged.
543
The attributes may or may not be populated, as determined by the
544
logging options and the log formatter capabilities.
547
def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
551
self.merge_depth = merge_depth
556
313
class LogFormatter(object):
557
"""Abstract class to display log messages.
559
At a minimum, a derived class must implement the log_revision method.
561
If the LogFormatter needs to be informed of the beginning or end of
562
a log it should implement the begin_log and/or end_log hook methods.
564
A LogFormatter should define the following supports_XXX flags
565
to indicate which LogRevision attributes it supports:
567
- supports_delta must be True if this log formatter supports delta.
568
Otherwise the delta attribute may not be populated.
569
- supports_merge_revisions must be True if this log formatter supports
570
merge revisions. If not, only mainline revisions (those
571
with merge_depth == 0) will be passed to the formatter.
572
- supports_tags must be True if this log formatter supports tags.
573
Otherwise the tags attribute may not be populated.
314
"""Abstract class to display log messages."""
576
316
def __init__(self, to_file, show_ids=False, show_timezone='original'):
577
317
self.to_file = to_file
578
318
self.show_ids = show_ids
579
319
self.show_timezone = show_timezone
581
# TODO: uncomment this block after show() has been removed.
582
# Until then defining log_revision would prevent _show_log calling show()
583
# in legacy formatters.
584
# def log_revision(self, revision):
587
# :param revision: The LogRevision to be logged.
589
# raise NotImplementedError('not implemented in abstract base')
591
@deprecated_method(zero_seventeen)
592
321
def show(self, revno, rev, delta):
593
322
raise NotImplementedError('not implemented in abstract base')
595
324
def short_committer(self, rev):
596
325
return re.sub('<.*@.*>', '', rev.committer).strip(' ')
599
328
class LongLogFormatter(LogFormatter):
601
supports_merge_revisions = True
602
supports_delta = True
605
@deprecated_method(zero_seventeen)
606
def show(self, revno, rev, delta, tags=None):
607
lr = LogRevision(rev, revno, 0, delta, tags)
608
return self.log_revision(lr)
610
@deprecated_method(zero_eleven)
329
def show(self, revno, rev, delta):
330
return self._show_helper(revno=revno, rev=rev, delta=delta)
611
332
def show_merge(self, rev, merge_depth):
612
lr = LogRevision(rev, merge_depth=merge_depth)
613
return self.log_revision(lr)
615
@deprecated_method(zero_seventeen)
616
def show_merge_revno(self, rev, merge_depth, revno, tags=None):
617
"""Show a merged revision rev, with merge_depth and a revno."""
618
lr = LogRevision(rev, revno, merge_depth, tags=tags)
619
return self.log_revision(lr)
621
def log_revision(self, revision):
622
"""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."""
623
337
from bzrlib.osutils import format_date
624
indent = ' '*revision.merge_depth
625
338
to_file = self.to_file
626
339
print >>to_file, indent+'-' * 60
627
if revision.revno is not None:
628
print >>to_file, indent+'revno:', revision.revno
630
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
631
346
if self.show_ids:
632
print >>to_file, indent+'revision-id:', revision.rev.revision_id
633
for parent_id in revision.rev.parent_ids:
347
for parent_id in rev.parent_ids:
634
348
print >>to_file, indent+'parent:', parent_id
635
print >>to_file, indent+'committer:', revision.rev.committer
349
print >>to_file, indent+'committer:', rev.committer
638
351
print >>to_file, indent+'branch nick: %s' % \
639
revision.rev.properties['branch-nick']
352
rev.properties['branch-nick']
642
date_str = format_date(revision.rev.timestamp,
643
revision.rev.timezone or 0,
355
date_str = format_date(rev.timestamp,
644
357
self.show_timezone)
645
358
print >>to_file, indent+'timestamp: %s' % date_str
647
360
print >>to_file, indent+'message:'
648
if not revision.rev.message:
649
362
print >>to_file, indent+' (no message)'
651
message = revision.rev.message.rstrip('\r\n')
364
message = rev.message.rstrip('\r\n')
652
365
for l in message.split('\n'):
653
366
print >>to_file, indent+' ' + l
654
if revision.delta is not None:
655
revision.delta.show(to_file, self.show_ids, indent=indent)
368
delta.show(to_file, self.show_ids)
658
371
class ShortLogFormatter(LogFormatter):
660
supports_delta = True
662
@deprecated_method(zero_seventeen)
663
372
def show(self, revno, rev, delta):
664
lr = LogRevision(rev, revno, 0, delta)
665
return self.log_revision(lr)
667
def log_revision(self, revision):
668
373
from bzrlib.osutils import format_date
670
375
to_file = self.to_file
671
date_str = format_date(revision.rev.timestamp,
672
revision.rev.timezone or 0,
675
if len(revision.rev.parent_ids) > 1:
676
is_merge = ' [merge]'
677
print >>to_file, "%5s %s\t%s%s" % (revision.revno,
678
self.short_committer(revision.rev),
679
format_date(revision.rev.timestamp,
680
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,
681
380
self.show_timezone, date_fmt="%Y-%m-%d",
684
382
if self.show_ids:
685
print >>to_file, ' revision-id:', revision.rev.revision_id
686
if not revision.rev.message:
383
print >>to_file, ' revision-id:', rev.revision_id
687
385
print >>to_file, ' (no message)'
689
message = revision.rev.message.rstrip('\r\n')
387
message = rev.message.rstrip('\r\n')
690
388
for l in message.split('\n'):
691
389
print >>to_file, ' ' + l
693
391
# TODO: Why not show the modified files in a shorter form as
694
392
# well? rewrap them single lines of appropriate length
695
if revision.delta is not None:
696
revision.delta.show(to_file, self.show_ids)
394
delta.show(to_file, self.show_ids)
697
395
print >>to_file, ''
700
398
class LineLogFormatter(LogFormatter):
702
def __init__(self, *args, **kwargs):
703
from bzrlib.osutils import terminal_width
704
super(LineLogFormatter, self).__init__(*args, **kwargs)
705
self._max_chars = terminal_width() - 1
707
399
def truncate(self, str, max_len):
708
400
if len(str) <= max_len:
752
439
lf = LineLogFormatter(None)
753
440
return lf.log_string(None, rev, max_chars)
756
class LogFormatterRegistry(registry.Registry):
757
"""Registry for log formatters"""
759
def make_formatter(self, name, *args, **kwargs):
760
"""Construct a formatter from arguments.
762
:param name: Name of the formatter to construct. 'short', 'long' and
765
return self.get(name)(*args, **kwargs)
767
def get_default(self, branch):
768
return self.get(branch.get_config().log_format())
771
log_formatter_registry = LogFormatterRegistry()
774
log_formatter_registry.register('short', ShortLogFormatter,
775
'Moderately short log format')
776
log_formatter_registry.register('long', LongLogFormatter,
777
'Detailed log format')
778
log_formatter_registry.register('line', LineLogFormatter,
779
'Log format with one line per revision')
443
'long': LongLogFormatter,
444
'short': ShortLogFormatter,
445
'line': LineLogFormatter,
782
448
def register_formatter(name, formatter):
783
log_formatter_registry.register(name, formatter)
449
FORMATTERS[name] = formatter
786
451
def log_formatter(name, *args, **kwargs):
787
452
"""Construct a formatter from arguments.