43
43
In verbose mode we show a summary of what changed in each particular
44
44
revision. Note that this is the delta for changes in that revision
45
relative to its left-most parent, not the delta relative to the last
45
relative to its mainline parent, not the delta relative to the last
46
46
logged revision. So for example if you ask for a verbose log of
47
47
changes touching hello.c you will get a list of those revisions also
48
48
listing other things that were changed in the same revision, but not
49
49
all the changes since the previous revision that touched hello.c.
53
from itertools import (
52
# TODO: option to show delta summaries for merged-in revisions
54
from itertools import izip
58
from warnings import (
62
57
from bzrlib import (
68
from bzrlib.errors import (
71
from bzrlib.osutils import (
73
get_terminal_encoding,
76
from bzrlib.revision import (
79
from bzrlib.revisionspec import (
61
import bzrlib.errors as errors
82
62
from bzrlib.symbol_versioning import (
86
67
from bzrlib.trace import mutter
210
197
mutter('get log for file_id %r', specific_fileid)
212
199
if search is not None:
213
201
searchRE = re.compile(search, re.IGNORECASE)
217
mainline_revs, rev_nos, start_rev_id, end_rev_id = \
218
_get_mainline_revs(branch, start_revision, end_revision)
219
if not mainline_revs:
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
if direction == 'reverse':
223
start_rev_id, end_rev_id = end_rev_id, start_rev_id
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])
225
231
legacy_lf = getattr(lf, 'log_revision', None) is None
227
233
# pre-0.17 formatters use show for mainline revisions.
243
249
generate_merge_revisions = getattr(lf, 'supports_merge_revisions',
245
generate_single_revision = False
246
if ((not generate_merge_revisions)
247
and ((start_rev_id and (start_rev_id not in rev_nos))
248
or (end_rev_id and (end_rev_id not in rev_nos)))):
249
generate_single_revision = ((start_rev_id == end_rev_id)
250
and getattr(lf, 'supports_single_merge_revision', False))
251
if not generate_single_revision:
252
raise BzrCommandError('Selected log formatter only supports '
253
'mainline revisions.')
254
generate_merge_revisions = generate_single_revision
255
251
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
256
252
direction, include_merges=generate_merge_revisions)
257
view_revisions = _filter_revision_range(list(view_revs_iter),
260
if view_revisions and generate_single_revision:
261
view_revisions = view_revisions[0:1]
262
253
if specific_fileid:
263
view_revisions = _filter_revisions_touching_file_id(branch,
254
view_revisions = _get_revisions_touching_file_id(branch,
259
view_revisions = list(view_revs_iter)
268
# rebase merge_depth - unless there are no revisions or
269
# either the first or last revision have merge_depth = 0.
270
if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
271
min_depth = min([d for r,n,d in view_revisions])
273
view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
275
261
rev_tag_dict = {}
276
262
generate_tags = getattr(lf, 'supports_tags', False)
277
263
if generate_tags:
283
269
def iter_revisions():
284
270
# r = revision, n = revno, d = merge depth
285
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)
287
274
repository = branch.repository
288
275
while revision_ids:
290
277
revisions = repository.get_revisions(revision_ids[:num])
291
278
if generate_delta:
292
deltas = repository.get_deltas_for_revisions(revisions)
293
cur_deltas = dict(izip((r.revision_id for r in revisions),
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))
295
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
296
288
yield revision, cur_deltas.get(revision.revision_id)
297
289
revision_ids = revision_ids[num:]
298
290
num = min(int(num * 1.5), 200)
335
def _get_mainline_revs(branch, start_revision, end_revision):
336
"""Get the mainline revisions from the branch.
338
Generates the list of mainline revisions for the branch.
340
:param branch: The branch containing the revisions.
342
:param start_revision: The first revision to be logged.
343
For backwards compatibility this may be a mainline integer revno,
344
but for merge revision support a RevisionInfo is expected.
346
:param end_revision: The last revision to be logged.
347
For backwards compatibility this may be a mainline integer revno,
348
but for merge revision support a RevisionInfo is expected.
350
:return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
352
which_revs = _enumerate_history(branch)
354
return None, None, None, None
356
# For mainline generation, map start_revision and end_revision to
357
# mainline revnos. If the revision is not on the mainline choose the
358
# appropriate extreme of the mainline instead - the extra will be
360
# Also map the revisions to rev_ids, to be used in the later filtering
363
if start_revision is None:
366
if isinstance(start_revision,RevisionInfo):
367
start_rev_id = start_revision.rev_id
368
start_revno = start_revision.revno or 1
370
branch.check_real_revno(start_revision)
371
start_revno = start_revision
374
if end_revision is None:
375
end_revno = len(which_revs)
377
if isinstance(end_revision,RevisionInfo):
378
end_rev_id = end_revision.rev_id
379
end_revno = end_revision.revno or len(which_revs)
381
branch.check_real_revno(end_revision)
382
end_revno = end_revision
384
if ((start_rev_id == NULL_REVISION)
385
or (end_rev_id == NULL_REVISION)):
386
raise BzrCommandError('Logging revision 0 is invalid.')
387
if start_revno > end_revno:
388
raise BzrCommandError("Start revision must be older than "
391
# list indexes are 0-based; revisions are 1-based
392
cut_revs = which_revs[(start_revno-1):(end_revno)]
394
return None, None, None, None
396
# convert the revision history to a dictionary:
397
rev_nos = dict((k, v) for v, k in cut_revs)
399
# override the mainline to look like the revision history.
400
mainline_revs = [revision_id for index, revision_id in cut_revs]
401
if cut_revs[0][0] == 1:
402
mainline_revs.insert(0, None)
404
mainline_revs.insert(0, which_revs[start_revno-2][1])
405
return mainline_revs, rev_nos, start_rev_id, end_rev_id
408
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
409
"""Filter view_revisions based on revision ranges.
411
:param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
412
tuples to be filtered.
414
:param start_rev_id: If not NONE specifies the first revision to be logged.
415
If NONE then all revisions up to the end_rev_id are logged.
417
:param end_rev_id: If not NONE specifies the last revision to be logged.
418
If NONE then all revisions up to the end of the log are logged.
420
:return: The filtered view_revisions.
422
if start_rev_id or end_rev_id:
423
revision_ids = [r for r, n, d in view_revisions]
425
start_index = revision_ids.index(start_rev_id)
428
if start_rev_id == end_rev_id:
429
end_index = start_index
432
end_index = revision_ids.index(end_rev_id)
434
end_index = len(view_revisions) - 1
435
# To include the revisions merged into the last revision,
436
# extend end_rev_id down to, but not including, the next rev
437
# with the same or lesser merge_depth
438
end_merge_depth = view_revisions[end_index][2]
440
for index in xrange(end_index+1, len(view_revisions)+1):
441
if view_revisions[index][2] <= end_merge_depth:
442
end_index = index - 1
445
# if the search falls off the end then log to the end as well
446
end_index = len(view_revisions) - 1
447
view_revisions = view_revisions[start_index:end_index+1]
448
return view_revisions
451
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
327
def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
453
329
"""Return the list of revision ids which touch a given file id.
455
The function filters view_revisions and returns a subset.
456
331
This includes the revisions which directly change the file id,
457
332
and the revisions which merge these changes. So if the
458
333
revision graph is::
653
520
def log_revision(self, revision):
654
521
"""Log a revision, either merged or not."""
655
indent = ' ' * revision.merge_depth
522
from bzrlib.osutils import format_date
523
indent = ' '*revision.merge_depth
656
524
to_file = self.to_file
657
to_file.write(indent + '-' * 60 + '\n')
525
print >>to_file, indent+'-' * 60
658
526
if revision.revno is not None:
659
to_file.write(indent + 'revno: %s\n' % (revision.revno,))
527
print >>to_file, indent+'revno:', revision.revno
660
528
if revision.tags:
661
to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
529
print >>to_file, indent+'tags: %s' % (', '.join(revision.tags))
662
530
if self.show_ids:
663
to_file.write(indent + 'revision-id:' + revision.rev.revision_id)
531
print >>to_file, indent+'revision-id:', revision.rev.revision_id
665
532
for parent_id in revision.rev.parent_ids:
666
to_file.write(indent + 'parent: %s\n' % (parent_id,))
668
author = revision.rev.properties.get('author', None)
669
if author is not None:
670
to_file.write(indent + 'author: %s\n' % (author,))
671
to_file.write(indent + 'committer: %s\n' % (revision.rev.committer,))
673
branch_nick = revision.rev.properties.get('branch-nick', None)
674
if branch_nick is not None:
675
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']
677
541
date_str = format_date(revision.rev.timestamp,
678
542
revision.rev.timezone or 0,
679
543
self.show_timezone)
680
to_file.write(indent + 'timestamp: %s\n' % (date_str,))
544
print >>to_file, indent+'timestamp: %s' % date_str
682
to_file.write(indent + 'message:\n')
546
print >>to_file, indent+'message:'
683
547
if not revision.rev.message:
684
to_file.write(indent + ' (no message)\n')
548
print >>to_file, indent+' (no message)'
686
550
message = revision.rev.message.rstrip('\r\n')
687
551
for l in message.split('\n'):
688
to_file.write(indent + ' %s\n' % (l,))
552
print >>to_file, indent+' ' + l
689
553
if revision.delta is not None:
690
revision.delta.show(to_file, self.show_ids, indent=indent)
554
revision.delta.show(to_file, self.show_ids)
693
557
class ShortLogFormatter(LogFormatter):
695
559
supports_delta = True
696
supports_single_merge_revision = True
698
561
@deprecated_method(zero_seventeen)
699
562
def show(self, revno, rev, delta):
709
574
if len(revision.rev.parent_ids) > 1:
710
575
is_merge = ' [merge]'
711
to_file.write("%5s %s\t%s%s\n" % (revision.revno,
712
self.short_author(revision.rev),
576
print >>to_file, "%5s %s\t%s%s" % (revision.revno,
577
self.short_committer(revision.rev),
713
578
format_date(revision.rev.timestamp,
714
579
revision.rev.timezone or 0,
715
580
self.show_timezone, date_fmt="%Y-%m-%d",
716
581
show_offset=False),
718
583
if self.show_ids:
719
to_file.write(' revision-id:%s\n' % (revision.rev.revision_id,))
584
print >>to_file, ' revision-id:', revision.rev.revision_id
720
585
if not revision.rev.message:
721
to_file.write(' (no message)\n')
586
print >>to_file, ' (no message)'
723
588
message = revision.rev.message.rstrip('\r\n')
724
589
for l in message.split('\n'):
725
to_file.write(' %s\n' % (l,))
590
print >>to_file, ' ' + l
727
592
# TODO: Why not show the modified files in a shorter form as
728
593
# well? rewrap them single lines of appropriate length
729
594
if revision.delta is not None:
730
595
revision.delta.show(to_file, self.show_ids)
734
599
class LineLogFormatter(LogFormatter):
736
supports_single_merge_revision = True
738
601
def __init__(self, *args, **kwargs):
602
from bzrlib.osutils import terminal_width
739
603
super(LineLogFormatter, self).__init__(*args, **kwargs)
740
604
self._max_chars = terminal_width() - 1
758
623
@deprecated_method(zero_seventeen)
759
624
def show(self, revno, rev, delta):
760
self.to_file.write(self.log_string(revno, rev, terminal_width()-1))
761
self.to_file.write('\n')
625
from bzrlib.osutils import terminal_width
626
print >> self.to_file, self.log_string(revno, rev, terminal_width()-1)
763
628
def log_revision(self, revision):
764
self.to_file.write(self.log_string(revision.revno, revision.rev,
766
self.to_file.write('\n')
629
print >>self.to_file, self.log_string(revision.revno, revision.rev,
768
632
def log_string(self, revno, rev, max_chars):
769
633
"""Format log info into one string. Truncate tail of string