62
from bzrlib.lazy_import import lazy_import
63
lazy_import(globals(), """
65
62
from bzrlib import (
68
repository as _mod_repository,
69
revision as _mod_revision,
68
from bzrlib.errors import (
79
71
from bzrlib.osutils import (
81
73
get_terminal_encoding,
76
from bzrlib.revision import (
79
from bzrlib.revisionspec import (
82
from bzrlib.symbol_versioning import (
86
from bzrlib.trace import mutter
87
from bzrlib.tsort import (
86
93
def find_touching_revisions(branch, file_id):
201
207
warn("not a LogFormatter instance: %r" % lf)
203
209
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)
210
mutter('get log for file_id %r', specific_fileid)
213
212
if search is not None:
214
213
searchRE = re.compile(search, re.IGNORECASE)
219
generate_tags = getattr(lf, 'supports_tags', False)
221
if branch.supports_tags():
222
rev_tag_dict = branch.tags.get_reverse_tag_dict()
224
generate_delta = verbose and getattr(lf, 'supports_delta', False)
226
# now we just print all the revisions
228
for (rev_id, revno, merge_depth), rev, delta in _iter_revisions(
229
branch.repository, view_revisions, generate_delta):
231
if not searchRE.search(rev.message):
234
lr = LogRevision(rev, revno, merge_depth, delta,
235
rev_tag_dict.get(rev_id))
239
if log_count >= limit:
243
def calculate_view_revisions(branch, start_revision, end_revision, direction,
244
specific_fileid, generate_merge_revisions,
245
allow_single_merge_revision):
246
if (not generate_merge_revisions and start_revision is end_revision is
247
None and direction == 'reverse' and specific_fileid is None):
248
return _linear_view_revisions(branch)
250
217
mainline_revs, rev_nos, start_rev_id, end_rev_id = \
251
218
_get_mainline_revs(branch, start_revision, end_revision)
252
219
if not mainline_revs:
255
222
if direction == 'reverse':
256
223
start_rev_id, end_rev_id = end_rev_id, start_rev_id
225
legacy_lf = getattr(lf, 'log_revision', None) is None
227
# pre-0.17 formatters use show for mainline revisions.
228
# how should we show merged revisions ?
229
# pre-0.11 api: show_merge
230
# 0.11-0.16 api: show_merge_revno
231
show_merge_revno = getattr(lf, 'show_merge_revno', None)
232
show_merge = getattr(lf, 'show_merge', None)
233
if show_merge is None and show_merge_revno is None:
234
# no merged-revno support
235
generate_merge_revisions = False
237
generate_merge_revisions = True
238
# tell developers to update their code
239
symbol_versioning.warn('LogFormatters should provide log_revision '
240
'instead of show and show_merge_revno since bzr 0.17.',
241
DeprecationWarning, stacklevel=3)
243
generate_merge_revisions = getattr(lf, 'supports_merge_revisions',
258
245
generate_single_revision = False
259
246
if ((not generate_merge_revisions)
260
247
and ((start_rev_id and (start_rev_id not in rev_nos))
261
248
or (end_rev_id and (end_rev_id not in rev_nos)))):
262
249
generate_single_revision = ((start_rev_id == end_rev_id)
263
and allow_single_merge_revision)
250
and getattr(lf, 'supports_single_merge_revision', False))
264
251
if not generate_single_revision:
265
raise errors.BzrCommandError('Selected log formatter only supports'
266
' mainline revisions.')
252
raise BzrCommandError('Selected log formatter only supports '
253
'mainline revisions.')
267
254
generate_merge_revisions = generate_single_revision
268
255
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
269
256
direction, include_merges=generate_merge_revisions)
284
271
min_depth = min([d for r,n,d in view_revisions])
285
272
if min_depth != 0:
286
273
view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
287
return view_revisions
290
def _linear_view_revisions(branch):
291
start_revno, start_revision_id = branch.last_revision_info()
292
repo = branch.repository
293
revision_ids = repo.iter_reverse_revision_history(start_revision_id)
294
for num, revision_id in enumerate(revision_ids):
295
yield revision_id, str(start_revno - num), 0
298
def _iter_revisions(repository, view_revisions, generate_delta):
300
view_revisions = iter(view_revisions)
302
cur_view_revisions = [d for x, d in zip(range(num), view_revisions)]
303
if len(cur_view_revisions) == 0:
276
generate_tags = getattr(lf, 'supports_tags', False)
278
if branch.supports_tags():
279
rev_tag_dict = branch.tags.get_reverse_tag_dict()
281
generate_delta = verbose and getattr(lf, 'supports_delta', False)
283
def iter_revisions():
306
284
# r = revision, n = revno, d = merge depth
307
revision_ids = [r for (r, n, d) in cur_view_revisions]
308
revisions = repository.get_revisions(revision_ids)
310
deltas = repository.get_deltas_for_revisions(revisions)
311
cur_deltas = dict(izip((r.revision_id for r in revisions),
313
for view_data, revision in izip(cur_view_revisions, revisions):
314
yield view_data, revision, cur_deltas.get(revision.revision_id)
315
num = min(int(num * 1.5), 200)
285
revision_ids = [r for r, n, d in view_revisions]
287
repository = branch.repository
290
revisions = repository.get_revisions(revision_ids[:num])
292
deltas = repository.get_deltas_for_revisions(revisions)
293
cur_deltas = dict(izip((r.revision_id for r in revisions),
295
for revision in revisions:
296
yield revision, cur_deltas.get(revision.revision_id)
297
revision_ids = revision_ids[num:]
298
num = min(int(num * 1.5), 200)
300
# now we just print all the revisions
302
for ((rev_id, revno, merge_depth), (rev, delta)) in \
303
izip(view_revisions, iter_revisions()):
306
if not searchRE.search(rev.message):
310
lr = LogRevision(rev, revno, merge_depth, delta,
311
rev_tag_dict.get(rev_id))
314
# support for legacy (pre-0.17) LogFormatters
317
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
319
lf.show(revno, rev, delta)
321
if show_merge_revno is None:
322
lf.show_merge(rev, merge_depth)
325
lf.show_merge_revno(rev, merge_depth, revno,
326
rev_tag_dict.get(rev_id))
328
lf.show_merge_revno(rev, merge_depth, revno)
331
if log_count >= limit:
318
335
def _get_mainline_revs(branch, start_revision, end_revision):
356
373
end_rev_id = None
357
374
if end_revision is None:
358
end_revno = branch_revno
375
end_revno = len(which_revs)
360
if isinstance(end_revision, revisionspec.RevisionInfo):
377
if isinstance(end_revision,RevisionInfo):
361
378
end_rev_id = end_revision.rev_id
362
end_revno = end_revision.revno or branch_revno
379
end_revno = end_revision.revno or len(which_revs)
364
381
branch.check_real_revno(end_revision)
365
382
end_revno = end_revision
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.')
384
if ((start_rev_id == NULL_REVISION)
385
or (end_rev_id == NULL_REVISION)):
386
raise BzrCommandError('Logging revision 0 is invalid.')
370
387
if start_revno > end_revno:
371
raise errors.BzrCommandError("Start revision must be older than "
388
raise BzrCommandError("Start revision must be older than "
374
if end_revno < start_revno:
391
# list indexes are 0-based; revisions are 1-based
392
cut_revs = which_revs[(start_revno-1):(end_revno)]
375
394
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)
394
mainline_revs.reverse()
396
# convert the revision history to a dictionary:
397
rev_nos = dict((k, v) for v, k in cut_revs)
396
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])
397
405
return mainline_revs, rev_nos, start_rev_id, end_rev_id
461
469
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
463
471
# find all the revisions that change the specific file
472
file_weave = branch.repository.weave_store.get_weave(file_id,
473
branch.repository.get_transaction())
474
weave_modifed_revisions = set(file_weave.versions())
464
475
# build the ancestry of each revision in the graph
465
476
# - only listing the ancestors that change the specific file.
466
graph = branch.repository.get_graph()
467
# This asks for all mainline revisions, which means we only have to spider
468
# sideways, rather than depth history. That said, its still size-of-history
469
# and should be addressed.
470
# mainline_revisions always includes an extra revision at the beginning, so
472
parent_map = dict(((key, value) for key, value in
473
graph.iter_ancestry(mainline_revisions[1:]) if value is not None))
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)
477
rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
478
sorted_rev_list = topo_sort(rev_graph)
478
480
for rev in sorted_rev_list:
479
text_key = (file_id, rev)
480
parents = parent_map[rev]
481
if text_key not in modified_text_versions and len(parents) == 1:
481
parents = rev_graph[rev]
482
if rev not in weave_modifed_revisions and len(parents) == 1:
482
483
# We will not be adding anything new, so just use a reference to
483
484
# the parent ancestry.
484
485
rev_ancestry = ancestry[parents[0]]
486
487
rev_ancestry = set()
487
if text_key in modified_text_versions:
488
if rev in weave_modifed_revisions:
488
489
rev_ancestry.add(rev)
489
490
for parent in parents:
490
if parent not in ancestry:
491
# parent is a Ghost, which won't be present in
492
# sorted_rev_list, but we may access it later, so create an
494
ancestry[parent] = set()
495
491
rev_ancestry = rev_ancestry.union(ancestry[parent])
496
492
ancestry[rev] = rev_ancestry
498
494
def is_merging_rev(r):
499
parents = parent_map[r]
495
parents = rev_graph[r]
500
496
if len(parents) > 1:
501
497
leftparent = parents[0]
502
498
for rightparent in parents[1:]:
524
520
for revision_id in revision_ids:
525
521
yield revision_id, str(rev_nos[revision_id]), 0
527
graph = branch.repository.get_graph()
528
# This asks for all mainline revisions, which means we only have to spider
529
# sideways, rather than depth history. That said, its still size-of-history
530
# and should be addressed.
531
# mainline_revisions always includes an extra revision at the beginning, so
533
parent_map = dict(((key, value) for key, value in
534
graph.iter_ancestry(mainline_revs[1:]) if value is not None))
535
# filter out ghosts; merge_sort errors on ghosts.
536
rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
537
merge_sorted_revisions = tsort.merge_sort(
523
merge_sorted_revisions = merge_sort(
524
branch.repository.get_revision_graph(mainline_revs[-1]),
539
525
mainline_revs[-1],
541
527
generate_revno=True)
664
639
supports_delta = True
665
640
supports_tags = True
642
@deprecated_method(zero_seventeen)
643
def show(self, revno, rev, delta, tags=None):
644
lr = LogRevision(rev, revno, 0, delta, tags)
645
return self.log_revision(lr)
647
@deprecated_method(zero_seventeen)
648
def show_merge_revno(self, rev, merge_depth, revno, tags=None):
649
"""Show a merged revision rev, with merge_depth and a revno."""
650
lr = LogRevision(rev, revno, merge_depth, tags=tags)
651
return self.log_revision(lr)
667
653
def log_revision(self, revision):
668
654
"""Log a revision, either merged or not."""
669
655
indent = ' ' * revision.merge_depth