199
186
"""Worker function for show_log - see show_log."""
187
from bzrlib.osutils import format_date
188
from bzrlib.errors import BzrCheckError
190
from warnings import warn
200
192
if not isinstance(lf, LogFormatter):
201
193
warn("not a LogFormatter instance: %r" % lf)
203
195
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)
214
generate_tags = getattr(lf, 'supports_tags', False)
216
if branch.supports_tags():
217
rev_tag_dict = branch.tags.get_reverse_tag_dict()
219
generate_delta = verbose and getattr(lf, 'supports_delta', False)
221
# 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:
227
lr = LogRevision(rev, revno, merge_depth, delta,
228
rev_tag_dict.get(rev_id))
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)
196
mutter('get log for file_id %r', specific_fileid)
198
if search is not None:
200
searchRE = re.compile(search, re.IGNORECASE)
243
204
mainline_revs, rev_nos, start_rev_id, end_rev_id = \
244
205
_get_mainline_revs(branch, start_revision, end_revision)
245
206
if not mainline_revs:
248
209
if direction == 'reverse':
249
210
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
212
legacy_lf = getattr(lf, 'log_revision', None) is None
214
# pre-0.17 formatters use show for mainline revisions.
215
# how should we show merged revisions ?
216
# pre-0.11 api: show_merge
217
# 0.11-0.16 api: show_merge_revno
218
show_merge_revno = getattr(lf, 'show_merge_revno', None)
219
show_merge = getattr(lf, 'show_merge', None)
220
if show_merge is None and show_merge_revno is None:
221
# no merged-revno support
222
generate_merge_revisions = False
224
generate_merge_revisions = True
225
# tell developers to update their code
226
symbol_versioning.warn('LogFormatters should provide log_revision '
227
'instead of show and show_merge_revno since bzr 0.17.',
228
DeprecationWarning, stacklevel=3)
230
generate_merge_revisions = getattr(lf, 'supports_merge_revisions',
261
232
view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
262
233
direction, include_merges=generate_merge_revisions)
263
234
view_revisions = _filter_revision_range(list(view_revs_iter),
266
if view_revisions and generate_single_revision:
267
view_revisions = view_revisions[0:1]
268
237
if specific_fileid:
269
238
view_revisions = _filter_revisions_touching_file_id(branch,
277
246
min_depth = min([d for r,n,d in view_revisions])
278
247
if min_depth != 0:
279
248
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)]
251
generate_tags = getattr(lf, 'supports_tags', False)
253
if branch.supports_tags():
254
rev_tag_dict = branch.tags.get_reverse_tag_dict()
256
generate_delta = verbose and getattr(lf, 'supports_delta', False)
258
def iter_revisions():
259
# r = revision, n = revno, d = merge depth
260
revision_ids = [r for r, n, d in view_revisions]
262
repository = branch.repository
265
revisions = repository.get_revisions(revision_ids[:num])
267
deltas = repository.get_deltas_for_revisions(revisions)
268
cur_deltas = dict(izip((r.revision_id for r in revisions),
270
for revision in revisions:
271
yield revision, cur_deltas.get(revision.revision_id)
272
revision_ids = revision_ids[num:]
273
num = min(int(num * 1.5), 200)
275
# now we just print all the revisions
277
for ((rev_id, revno, merge_depth), (rev, delta)) in \
278
izip(view_revisions, iter_revisions()):
281
if not searchRE.search(rev.message):
285
lr = LogRevision(rev, revno, merge_depth, delta,
286
rev_tag_dict.get(rev_id))
289
# support for legacy (pre-0.17) LogFormatters
292
lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
294
lf.show(revno, rev, delta)
296
if show_merge_revno is None:
297
lf.show_merge(rev, merge_depth)
300
lf.show_merge_revno(rev, merge_depth, revno,
301
rev_tag_dict.get(rev_id))
303
lf.show_merge_revno(rev, merge_depth, revno)
306
if log_count >= limit:
411
num = min(int(num * 1.5), 200)
414
310
def _get_mainline_revs(branch, start_revision, end_revision):
452
348
end_rev_id = None
453
349
if end_revision is None:
454
end_revno = branch_revno
350
end_revno = len(which_revs)
456
if isinstance(end_revision, revisionspec.RevisionInfo):
352
if isinstance(end_revision,RevisionInfo):
457
353
end_rev_id = end_revision.rev_id
458
end_revno = end_revision.revno or branch_revno
354
end_revno = end_revision.revno or len(which_revs)
460
356
branch.check_real_revno(end_revision)
461
357
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
359
if start_revno > end_revno:
467
raise errors.BzrCommandError("Start revision must be older than "
360
from bzrlib.errors import BzrCommandError
361
raise BzrCommandError("Start revision must be older than "
470
if end_revno < start_revno:
364
# list indexes are 0-based; revisions are 1-based
365
cut_revs = which_revs[(start_revno-1):(end_revno)]
471
367
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()
369
# convert the revision history to a dictionary:
370
rev_nos = dict((k, v) for v, k in cut_revs)
492
372
# override the mainline to look like the revision history.
373
mainline_revs = [revision_id for index, revision_id in cut_revs]
374
if cut_revs[0][0] == 1:
375
mainline_revs.insert(0, None)
377
mainline_revs.insert(0, which_revs[start_revno-2][1])
493
378
return mainline_revs, rev_nos, start_rev_id, end_rev_id
557
442
:return: A list of (revision_id, dotted_revno, merge_depth) tuples.
559
444
# find all the revisions that change the specific file
445
file_weave = branch.repository.weave_store.get_weave(file_id,
446
branch.repository.get_transaction())
447
weave_modifed_revisions = set(file_weave.versions())
560
448
# build the ancestry of each revision in the graph
561
449
# - only listing the ancestors that change the specific file.
562
graph = branch.repository.get_graph()
563
# This asks for all mainline revisions, which means we only have to spider
564
# sideways, rather than depth history. That said, its still size-of-history
565
# and should be addressed.
566
# mainline_revisions always includes an extra revision at the beginning, so
568
parent_map = dict(((key, value) for key, value in
569
graph.iter_ancestry(mainline_revisions[1:]) if value is not None))
570
sorted_rev_list = tsort.topo_sort(parent_map.items())
571
text_keys = [(file_id, rev_id) for rev_id in sorted_rev_list]
572
modified_text_versions = branch.repository.texts.get_parent_map(text_keys)
450
rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
451
sorted_rev_list = topo_sort(rev_graph)
574
453
for rev in sorted_rev_list:
575
text_key = (file_id, rev)
576
parents = parent_map[rev]
577
if text_key not in modified_text_versions and len(parents) == 1:
454
parents = rev_graph[rev]
455
if rev not in weave_modifed_revisions and len(parents) == 1:
578
456
# We will not be adding anything new, so just use a reference to
579
457
# the parent ancestry.
580
458
rev_ancestry = ancestry[parents[0]]
582
460
rev_ancestry = set()
583
if text_key in modified_text_versions:
461
if rev in weave_modifed_revisions:
584
462
rev_ancestry.add(rev)
585
463
for parent in parents:
586
if parent not in ancestry:
587
# parent is a Ghost, which won't be present in
588
# sorted_rev_list, but we may access it later, so create an
590
ancestry[parent] = set()
591
464
rev_ancestry = rev_ancestry.union(ancestry[parent])
592
465
ancestry[rev] = rev_ancestry
594
467
def is_merging_rev(r):
595
parents = parent_map[r]
468
parents = rev_graph[r]
596
469
if len(parents) > 1:
597
470
leftparent = parents[0]
598
471
for rightparent in parents[1:]: