62
from bzrlib.lazy_import import lazy_import
63
lazy_import(globals(), """
62
65
from bzrlib import (
68
repository as _mod_repository,
69
revision as _mod_revision,
67
from bzrlib.errors import (
70
79
from bzrlib.osutils import (
72
81
get_terminal_encoding,
75
from bzrlib.repository import _strip_NULL_ghosts
76
from bzrlib.revision import (
79
from bzrlib.revisionspec import (
82
from bzrlib.trace import mutter
83
from bzrlib.tsort import (
89
86
def find_touching_revisions(branch, file_id):
204
201
warn("not a LogFormatter instance: %r" % lf)
206
203
if specific_fileid:
207
mutter('get log for file_id %r', specific_fileid)
204
trace.mutter('get log for file_id %r', specific_fileid)
208
205
generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
209
206
allow_single_merge_revision = getattr(lf,
210
207
'supports_single_merge_revision', False)
265
262
generate_single_revision = ((start_rev_id == end_rev_id)
266
263
and allow_single_merge_revision)
267
264
if not generate_single_revision:
268
raise BzrCommandError('Selected log formatter only supports '
269
'mainline revisions.')
265
raise errors.BzrCommandError('Selected log formatter only supports'
266
' mainline revisions.')
270
267
generate_merge_revisions = generate_single_revision
271
268
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
272
269
direction, include_merges=generate_merge_revisions)
336
333
:return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
338
which_revs = _enumerate_history(branch)
335
branch_revno, branch_last_revision = branch.last_revision_info()
336
if branch_revno == 0:
340
337
return None, None, None, None
342
339
# For mainline generation, map start_revision and end_revision to
349
346
if start_revision is None:
352
if isinstance(start_revision,RevisionInfo):
349
if isinstance(start_revision, revisionspec.RevisionInfo):
353
350
start_rev_id = start_revision.rev_id
354
351
start_revno = start_revision.revno or 1
359
356
end_rev_id = None
360
357
if end_revision is None:
361
end_revno = len(which_revs)
358
end_revno = branch_revno
363
if isinstance(end_revision,RevisionInfo):
360
if isinstance(end_revision, revisionspec.RevisionInfo):
364
361
end_rev_id = end_revision.rev_id
365
end_revno = end_revision.revno or len(which_revs)
362
end_revno = end_revision.revno or branch_revno
367
364
branch.check_real_revno(end_revision)
368
365
end_revno = end_revision
370
if ((start_rev_id == NULL_REVISION)
371
or (end_rev_id == NULL_REVISION)):
372
raise BzrCommandError('Logging revision 0 is invalid.')
367
if ((start_rev_id == _mod_revision.NULL_REVISION)
368
or (end_rev_id == _mod_revision.NULL_REVISION)):
369
raise errors.BzrCommandError('Logging revision 0 is invalid.')
373
370
if start_revno > end_revno:
374
raise BzrCommandError("Start revision must be older than "
371
raise errors.BzrCommandError("Start revision must be older than "
377
# list indexes are 0-based; revisions are 1-based
378
cut_revs = which_revs[(start_revno-1):(end_revno)]
374
if end_revno < start_revno:
380
375
return None, None, None, None
376
cur_revno = branch_revno
379
for revision_id in branch.repository.iter_reverse_revision_history(
380
branch_last_revision):
381
if cur_revno < start_revno:
382
# We have gone far enough, but we always add 1 more revision
383
rev_nos[revision_id] = cur_revno
384
mainline_revs.append(revision_id)
386
if cur_revno <= end_revno:
387
rev_nos[revision_id] = cur_revno
388
mainline_revs.append(revision_id)
391
# We walked off the edge of all revisions, so we add a 'None' marker
392
mainline_revs.append(None)
382
# convert the revision history to a dictionary:
383
rev_nos = dict((k, v) for v, k in cut_revs)
394
mainline_revs.reverse()
385
396
# override the mainline to look like the revision history.
386
mainline_revs = [revision_id for index, revision_id in cut_revs]
387
if cut_revs[0][0] == 1:
388
mainline_revs.insert(0, None)
390
mainline_revs.insert(0, which_revs[start_revno-2][1])
391
397
return mainline_revs, rev_nos, start_rev_id, end_rev_id
455
461
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
457
463
# find all the revisions that change the specific file
458
file_weave = branch.repository.weave_store.get_weave(file_id,
459
branch.repository.get_transaction())
460
weave_modifed_revisions = set(file_weave.versions())
461
464
# build the ancestry of each revision in the graph
462
465
# - only listing the ancestors that change the specific file.
463
466
graph = branch.repository.get_graph()
468
471
# don't request it.
469
472
parent_map = dict(((key, value) for key, value in
470
473
graph.iter_ancestry(mainline_revisions[1:]) if value is not None))
471
sorted_rev_list = topo_sort(parent_map.items())
474
sorted_rev_list = tsort.topo_sort(parent_map.items())
475
text_keys = [(file_id, rev_id) for rev_id in sorted_rev_list]
476
modified_text_versions = branch.repository.texts.get_parent_map(text_keys)
473
478
for rev in sorted_rev_list:
479
text_key = (file_id, rev)
474
480
parents = parent_map[rev]
475
if rev not in weave_modifed_revisions and len(parents) == 1:
481
if text_key not in modified_text_versions and len(parents) == 1:
476
482
# We will not be adding anything new, so just use a reference to
477
483
# the parent ancestry.
478
484
rev_ancestry = ancestry[parents[0]]
480
486
rev_ancestry = set()
481
if rev in weave_modifed_revisions:
487
if text_key in modified_text_versions:
482
488
rev_ancestry.add(rev)
483
489
for parent in parents:
484
490
if parent not in ancestry:
502
508
# filter from the view the revisions that did not change or merge
503
509
# the specific file
504
510
return [(r, n, d) for r, n, d in view_revs_iter
505
if r in weave_modifed_revisions or is_merging_rev(r)]
511
if (file_id, r) in modified_text_versions or is_merging_rev(r)]
508
514
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
522
528
# This asks for all mainline revisions, which means we only have to spider
523
529
# sideways, rather than depth history. That said, its still size-of-history
524
530
# and should be addressed.
531
# mainline_revisions always includes an extra revision at the beginning, so
525
533
parent_map = dict(((key, value) for key, value in
526
graph.iter_ancestry(mainline_revs) if value is not None))
534
graph.iter_ancestry(mainline_revs[1:]) if value is not None))
527
535
# filter out ghosts; merge_sort errors on ghosts.
528
rev_graph = _strip_NULL_ghosts(parent_map)
529
merge_sorted_revisions = merge_sort(
536
rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
537
merge_sorted_revisions = tsort.merge_sort(
531
539
mainline_revs[-1],
604
612
only relevant if supports_merge_revisions is not True.
605
613
- supports_tags must be True if this log formatter supports tags.
606
614
Otherwise the tags attribute may not be populated.
616
Plugins can register functions to show custom revision properties using
617
the properties_handler_registry. The registered function
618
must respect the following interface description:
619
def my_show_properties(properties_dict):
620
# code that returns a dict {'name':'value'} of the properties
609
624
def __init__(self, to_file, show_ids=False, show_timezone='original'):
651
def show_properties(self, revision, indent):
652
"""Displays the custom properties returned by each registered handler.
654
If a registered handler raises an error it is propagated.
656
for key, handler in properties_handler_registry.iteritems():
657
for key, value in handler(revision).items():
658
self.to_file.write(indent + key + ': ' + value + '\n')
637
661
class LongLogFormatter(LogFormatter):
808
830
return log_formatter_registry.make_formatter(name, *args, **kwargs)
810
raise BzrCommandError("unknown log formatter: %r" % name)
832
raise errors.BzrCommandError("unknown log formatter: %r" % name)
813
835
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):