199
187
"""Worker function for show_log - see show_log."""
188
from bzrlib.osutils import format_date
189
from bzrlib.errors import BzrCheckError
191
from warnings import warn
200
193
if not isinstance(lf, LogFormatter):
201
194
warn("not a LogFormatter instance: %r" % lf)
203
196
if specific_fileid:
204
trace.mutter('get log for file_id %r', specific_fileid)
205
generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
206
allow_single_merge_revision = getattr(lf,
207
'supports_single_merge_revision', False)
208
view_revisions = calculate_view_revisions(branch, start_revision,
209
end_revision, direction,
211
generate_merge_revisions,
212
allow_single_merge_revision)
197
mutter('get log for file_id %r', specific_fileid)
199
if search is not None:
201
searchRE = re.compile(search, re.IGNORECASE)
205
which_revs = _enumerate_history(branch)
207
if start_revision is None:
210
branch.check_real_revno(start_revision)
212
if end_revision is None:
213
end_revision = len(which_revs)
215
branch.check_real_revno(end_revision)
217
# list indexes are 0-based; revisions are 1-based
218
cut_revs = which_revs[(start_revision-1):(end_revision)]
222
# convert the revision history to a dictionary:
223
rev_nos = dict((k, v) for v, k in cut_revs)
225
# override the mainline to look like the revision history.
226
mainline_revs = [revision_id for index, revision_id in cut_revs]
227
if cut_revs[0][0] == 1:
228
mainline_revs.insert(0, None)
230
mainline_revs.insert(0, which_revs[start_revision-2][1])
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)
213
261
rev_tag_dict = {}
214
262
generate_tags = getattr(lf, 'supports_tags', False)
215
263
if generate_tags:
219
267
generate_delta = verbose and getattr(lf, 'supports_delta', False)
269
def iter_revisions():
270
# r = revision, n = revno, d = merge depth
271
revision_ids = [r for r, n, d in view_revisions]
272
zeros = set(r for r, n, d in view_revisions if d == 0)
274
repository = branch.repository
277
revisions = repository.get_revisions(revision_ids[:num])
279
delta_revisions = [r for r in revisions if
280
r.revision_id in zeros]
281
deltas = repository.get_deltas_for_revisions(delta_revisions)
282
cur_deltas = dict(izip((r.revision_id for r in
283
delta_revisions), deltas))
284
for revision in revisions:
285
# The delta value will be None unless
286
# 1. verbose is specified, and
287
# 2. the revision is a mainline revision
288
yield revision, cur_deltas.get(revision.revision_id)
289
revision_ids = revision_ids[num:]
290
num = min(int(num * 1.5), 200)
221
292
# now we just print all the revisions
223
revision_iterator = make_log_rev_iterator(branch, view_revisions,
224
generate_delta, search)
225
for revs in revision_iterator:
226
for (rev_id, revno, merge_depth), rev, delta in revs:
294
for ((rev_id, revno, merge_depth), (rev, delta)) in \
295
izip(view_revisions, iter_revisions()):
298
if not searchRE.search(rev.message):
227
302
lr = LogRevision(rev, revno, merge_depth, delta,
228
303
rev_tag_dict.get(rev_id))
229
304
lf.log_revision(lr)
232
if log_count >= limit:
236
def calculate_view_revisions(branch, start_revision, end_revision, direction,
237
specific_fileid, generate_merge_revisions,
238
allow_single_merge_revision):
239
if (not generate_merge_revisions and start_revision is end_revision is
240
None and direction == 'reverse' and specific_fileid is None):
241
return _linear_view_revisions(branch)
243
mainline_revs, rev_nos, start_rev_id, end_rev_id = \
244
_get_mainline_revs(branch, start_revision, end_revision)
245
if not mainline_revs:
248
if direction == 'reverse':
249
start_rev_id, end_rev_id = end_rev_id, start_rev_id
251
generate_single_revision = False
252
if ((not generate_merge_revisions)
253
and ((start_rev_id and (start_rev_id not in rev_nos))
254
or (end_rev_id and (end_rev_id not in rev_nos)))):
255
generate_single_revision = ((start_rev_id == end_rev_id)
256
and allow_single_merge_revision)
257
if not generate_single_revision:
258
raise errors.BzrCommandError('Selected log formatter only supports'
259
' mainline revisions.')
260
generate_merge_revisions = generate_single_revision
261
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
262
direction, include_merges=generate_merge_revisions)
263
view_revisions = _filter_revision_range(list(view_revs_iter),
266
if view_revisions and generate_single_revision:
267
view_revisions = view_revisions[0:1]
269
view_revisions = _filter_revisions_touching_file_id(branch,
274
# rebase merge_depth - unless there are no revisions or
275
# either the first or last revision have merge_depth = 0.
276
if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
277
min_depth = min([d for r,n,d in view_revisions])
279
view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
280
return view_revisions
283
def _linear_view_revisions(branch):
284
start_revno, start_revision_id = branch.last_revision_info()
285
repo = branch.repository
286
revision_ids = repo.iter_reverse_revision_history(start_revision_id)
287
for num, revision_id in enumerate(revision_ids):
288
yield revision_id, str(start_revno - num), 0
291
def make_log_rev_iterator(branch, view_revisions, generate_delta, search):
292
"""Create a revision iterator for log.
294
:param branch: The branch being logged.
295
:param view_revisions: The revisions being viewed.
296
:param generate_delta: Whether to generate a delta for each revision.
297
:param search: A user text search string.
298
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
301
# Convert view_revisions into (view, None, None) groups to fit with
302
# the standard interface here.
303
if type(view_revisions) == list:
304
# A single batch conversion is faster than many incremental ones.
305
# As we have all the data, do a batch conversion.
306
nones = [None] * len(view_revisions)
307
log_rev_iterator = iter([zip(view_revisions, nones, nones)])
310
for view in view_revisions:
311
yield (view, None, None)
312
log_rev_iterator = iter([_convert()])
313
for adapter in log_adapters:
314
log_rev_iterator = adapter(branch, generate_delta, search,
316
return log_rev_iterator
319
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
320
"""Create a filtered iterator of log_rev_iterator matching on a regex.
322
:param branch: The branch being logged.
323
:param generate_delta: Whether to generate a delta for each revision.
324
:param search: A user text search string.
325
:param log_rev_iterator: An input iterator containing all revisions that
326
could be displayed, in lists.
327
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
331
return log_rev_iterator
332
# Compile the search now to get early errors.
333
searchRE = re.compile(search, re.IGNORECASE)
334
return _filter_message_re(searchRE, log_rev_iterator)
337
def _filter_message_re(searchRE, log_rev_iterator):
338
for revs in log_rev_iterator:
340
for (rev_id, revno, merge_depth), rev, delta in revs:
341
if searchRE.search(rev.message):
342
new_revs.append(((rev_id, revno, merge_depth), rev, delta))
346
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator):
347
"""Add revision deltas to a log iterator if needed.
349
:param branch: The branch being logged.
350
:param generate_delta: Whether to generate a delta for each revision.
351
:param search: A user text search string.
352
:param log_rev_iterator: An input iterator containing all revisions that
353
could be displayed, in lists.
354
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
357
if not generate_delta:
358
return log_rev_iterator
359
return _generate_deltas(branch.repository, log_rev_iterator)
362
def _generate_deltas(repository, log_rev_iterator):
363
"""Create deltas for each batch of revisions in log_rev_iterator."""
364
for revs in log_rev_iterator:
365
revisions = [rev[1] for rev in revs]
366
deltas = repository.get_deltas_for_revisions(revisions)
367
revs = [(rev[0], rev[1], delta) for rev, delta in izip(revs, deltas)]
371
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
372
"""Extract revision objects from the repository
374
:param branch: The branch being logged.
375
:param generate_delta: Whether to generate a delta for each revision.
376
:param search: A user text search string.
377
:param log_rev_iterator: An input iterator containing all revisions that
378
could be displayed, in lists.
379
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
382
repository = branch.repository
383
for revs in log_rev_iterator:
384
# r = revision_id, n = revno, d = merge depth
385
revision_ids = [view[0] for view, _, _ in revs]
386
revisions = repository.get_revisions(revision_ids)
387
revs = [(rev[0], revision, rev[2]) for rev, revision in
388
izip(revs, revisions)]
392
def _make_batch_filter(branch, generate_delta, search, log_rev_iterator):
393
"""Group up a single large batch into smaller ones.
395
:param branch: The branch being logged.
396
:param generate_delta: Whether to generate a delta for each revision.
397
:param search: A user text search string.
398
:param log_rev_iterator: An input iterator containing all revisions that
399
could be displayed, in lists.
400
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev, delta).
402
repository = branch.repository
404
for batch in log_rev_iterator:
407
step = [detail for _, detail in zip(range(num), batch)]
306
# support for legacy (pre-0.17) LogFormatters
309
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
311
lf.show(revno, rev, delta)
313
if show_merge_revno is None:
314
lf.show_merge(rev, merge_depth)
317
lf.show_merge_revno(rev, merge_depth, revno,
318
rev_tag_dict.get(rev_id))
320
lf.show_merge_revno(rev, merge_depth, revno)
323
if log_count >= limit:
411
num = min(int(num * 1.5), 200)
414
def _get_mainline_revs(branch, start_revision, end_revision):
415
"""Get the mainline revisions from the branch.
417
Generates the list of mainline revisions for the branch.
419
:param branch: The branch containing the revisions.
421
:param start_revision: The first revision to be logged.
422
For backwards compatibility this may be a mainline integer revno,
423
but for merge revision support a RevisionInfo is expected.
425
:param end_revision: The last revision to be logged.
426
For backwards compatibility this may be a mainline integer revno,
427
but for merge revision support a RevisionInfo is expected.
429
:return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
431
branch_revno, branch_last_revision = branch.last_revision_info()
432
if branch_revno == 0:
433
return None, None, None, None
435
# For mainline generation, map start_revision and end_revision to
436
# mainline revnos. If the revision is not on the mainline choose the
437
# appropriate extreme of the mainline instead - the extra will be
439
# Also map the revisions to rev_ids, to be used in the later filtering
442
if start_revision is None:
445
if isinstance(start_revision, revisionspec.RevisionInfo):
446
start_rev_id = start_revision.rev_id
447
start_revno = start_revision.revno or 1
449
branch.check_real_revno(start_revision)
450
start_revno = start_revision
453
if end_revision is None:
454
end_revno = branch_revno
456
if isinstance(end_revision, revisionspec.RevisionInfo):
457
end_rev_id = end_revision.rev_id
458
end_revno = end_revision.revno or branch_revno
460
branch.check_real_revno(end_revision)
461
end_revno = end_revision
463
if ((start_rev_id == _mod_revision.NULL_REVISION)
464
or (end_rev_id == _mod_revision.NULL_REVISION)):
465
raise errors.BzrCommandError('Logging revision 0 is invalid.')
466
if start_revno > end_revno:
467
raise errors.BzrCommandError("Start revision must be older than "
470
if end_revno < start_revno:
471
return None, None, None, None
472
cur_revno = branch_revno
475
for revision_id in branch.repository.iter_reverse_revision_history(
476
branch_last_revision):
477
if cur_revno < start_revno:
478
# We have gone far enough, but we always add 1 more revision
479
rev_nos[revision_id] = cur_revno
480
mainline_revs.append(revision_id)
482
if cur_revno <= end_revno:
483
rev_nos[revision_id] = cur_revno
484
mainline_revs.append(revision_id)
487
# We walked off the edge of all revisions, so we add a 'None' marker
488
mainline_revs.append(None)
490
mainline_revs.reverse()
492
# override the mainline to look like the revision history.
493
return mainline_revs, rev_nos, start_rev_id, end_rev_id
496
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
497
"""Filter view_revisions based on revision ranges.
499
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
500
tuples to be filtered.
502
:param start_rev_id: If not NONE specifies the first revision to be logged.
503
If NONE then all revisions up to the end_rev_id are logged.
505
:param end_rev_id: If not NONE specifies the last revision to be logged.
506
If NONE then all revisions up to the end of the log are logged.
508
:return: The filtered view_revisions.
510
if start_rev_id or end_rev_id:
511
revision_ids = [r for r, n, d in view_revisions]
513
start_index = revision_ids.index(start_rev_id)
516
if start_rev_id == end_rev_id:
517
end_index = start_index
520
end_index = revision_ids.index(end_rev_id)
522
end_index = len(view_revisions) - 1
523
# To include the revisions merged into the last revision,
524
# extend end_rev_id down to, but not including, the next rev
525
# with the same or lesser merge_depth
526
end_merge_depth = view_revisions[end_index][2]
528
for index in xrange(end_index+1, len(view_revisions)+1):
529
if view_revisions[index][2] <= end_merge_depth:
530
end_index = index - 1
533
# if the search falls off the end then log to the end as well
534
end_index = len(view_revisions) - 1
535
view_revisions = view_revisions[start_index:end_index+1]
536
return view_revisions
539
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
327
def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
541
329
"""Return the list of revision ids which touch a given file id.
543
The function filters view_revisions and returns a subset.
544
331
This includes the revisions which directly change the file id,
545
332
and the revisions which merge these changes. So if the
546
333
revision graph is::
760
501
supports_delta = True
761
502
supports_tags = True
504
@deprecated_method(zero_seventeen)
505
def show(self, revno, rev, delta, tags=None):
506
lr = LogRevision(rev, revno, 0, delta, tags)
507
return self.log_revision(lr)
509
@deprecated_method(zero_eleven)
510
def show_merge(self, rev, merge_depth):
511
lr = LogRevision(rev, merge_depth=merge_depth)
512
return self.log_revision(lr)
514
@deprecated_method(zero_seventeen)
515
def show_merge_revno(self, rev, merge_depth, revno, tags=None):
516
"""Show a merged revision rev, with merge_depth and a revno."""
517
lr = LogRevision(rev, revno, merge_depth, tags=tags)
518
return self.log_revision(lr)
763
520
def log_revision(self, revision):
764
521
"""Log a revision, either merged or not."""
765
indent = ' ' * revision.merge_depth
522
from bzrlib.osutils import format_date
523
indent = ' '*revision.merge_depth
766
524
to_file = self.to_file
767
to_file.write(indent + '-' * 60 + '\n')
525
print >>to_file, indent+'-' * 60
768
526
if revision.revno is not None:
769
to_file.write(indent + 'revno: %s\n' % (revision.revno,))
527
print >>to_file, indent+'revno:', revision.revno
770
528
if revision.tags:
771
to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
529
print >>to_file, indent+'tags: %s' % (', '.join(revision.tags))
772
530
if self.show_ids:
773
to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
531
print >>to_file, indent+'revision-id:', revision.rev.revision_id
775
532
for parent_id in revision.rev.parent_ids:
776
to_file.write(indent + 'parent: %s\n' % (parent_id,))
777
self.show_properties(revision.rev, indent)
779
author = revision.rev.properties.get('author', None)
780
if author is not None:
781
to_file.write(indent + 'author: %s\n' % (author,))
782
to_file.write(indent + 'committer: %s\n' % (revision.rev.committer,))
784
branch_nick = revision.rev.properties.get('branch-nick', None)
785
if branch_nick is not None:
786
to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))
533
print >>to_file, indent+'parent:', parent_id
534
print >>to_file, indent+'committer:', revision.rev.committer
537
print >>to_file, indent+'branch nick: %s' % \
538
revision.rev.properties['branch-nick']
788
541
date_str = format_date(revision.rev.timestamp,
789
542
revision.rev.timezone or 0,
790
543
self.show_timezone)
791
to_file.write(indent + 'timestamp: %s\n' % (date_str,))
544
print >>to_file, indent+'timestamp: %s' % date_str
793
to_file.write(indent + 'message:\n')
546
print >>to_file, indent+'message:'
794
547
if not revision.rev.message:
795
to_file.write(indent + ' (no message)\n')
548
print >>to_file, indent+' (no message)'
797
550
message = revision.rev.message.rstrip('\r\n')
798
551
for l in message.split('\n'):
799
to_file.write(indent + ' %s\n' % (l,))
552
print >>to_file, indent+' ' + l
800
553
if revision.delta is not None:
801
revision.delta.show(to_file, self.show_ids, indent=indent)
554
revision.delta.show(to_file, self.show_ids)
804
557
class ShortLogFormatter(LogFormatter):
806
559
supports_delta = True
807
supports_single_merge_revision = True
561
@deprecated_method(zero_seventeen)
562
def show(self, revno, rev, delta):
563
lr = LogRevision(rev, revno, 0, delta)
564
return self.log_revision(lr)
809
566
def log_revision(self, revision):
567
from bzrlib.osutils import format_date
810
569
to_file = self.to_file
570
date_str = format_date(revision.rev.timestamp,
571
revision.rev.timezone or 0,
812
574
if len(revision.rev.parent_ids) > 1:
813
575
is_merge = ' [merge]'
814
to_file.write("%5s %s\t%s%s\n" % (revision.revno,
815
self.short_author(revision.rev),
576
print >>to_file, "%5s %s\t%s%s" % (revision.revno,
577
self.short_committer(revision.rev),
816
578
format_date(revision.rev.timestamp,
817
579
revision.rev.timezone or 0,
818
580
self.show_timezone, date_fmt="%Y-%m-%d",
819
581
show_offset=False),
821
583
if self.show_ids:
822
to_file.write(' revision-id:%s\n' % (revision.rev.revision_id,))
584
print >>to_file, ' revision-id:', revision.rev.revision_id
823
585
if not revision.rev.message:
824
to_file.write(' (no message)\n')
586
print >>to_file, ' (no message)'
826
588
message = revision.rev.message.rstrip('\r\n')
827
589
for l in message.split('\n'):
828
to_file.write(' %s\n' % (l,))
590
print >>to_file, ' ' + l
830
592
# TODO: Why not show the modified files in a shorter form as
831
593
# well? rewrap them single lines of appropriate length
832
594
if revision.delta is not None:
833
595
revision.delta.show(to_file, self.show_ids)
837
599
class LineLogFormatter(LogFormatter):
839
supports_single_merge_revision = True
841
601
def __init__(self, *args, **kwargs):
602
from bzrlib.osutils import terminal_width
842
603
super(LineLogFormatter, self).__init__(*args, **kwargs)
843
604
self._max_chars = terminal_width() - 1