414
410
Subclasses may wish to override this.
416
return _DefaultLogGenerator(branch, rqst)
412
return _DefaultLogGenerator(branch, **rqst)
415
def _log_revision_iterator_using_per_file_graph(
416
branch, delta_type, match, levels, path, start_rev_id, end_rev_id,
417
direction, exclude_common_ancestry):
418
# Get the base revisions, filtering by the revision range.
419
# Note that we always generate the merge revisions because
420
# filter_revisions_touching_path() requires them ...
421
view_revisions = _calc_view_revisions(
422
branch, start_rev_id, end_rev_id,
423
direction, generate_merge_revisions=True,
424
exclude_common_ancestry=exclude_common_ancestry)
425
if not isinstance(view_revisions, list):
426
view_revisions = list(view_revisions)
427
view_revisions = _filter_revisions_touching_path(
428
branch, path, view_revisions,
429
include_merges=levels != 1)
430
return make_log_rev_iterator(
431
branch, view_revisions, delta_type, match)
434
def _log_revision_iterator_using_delta_matching(
435
branch, delta_type, match, levels, specific_files, start_rev_id, end_rev_id,
436
direction, exclude_common_ancestry, limit):
437
# Get the base revisions, filtering by the revision range
438
generate_merge_revisions = levels != 1
439
delayed_graph_generation = not specific_files and (
440
limit or start_rev_id or end_rev_id)
441
view_revisions = _calc_view_revisions(
442
branch, start_rev_id, end_rev_id,
444
generate_merge_revisions=generate_merge_revisions,
445
delayed_graph_generation=delayed_graph_generation,
446
exclude_common_ancestry=exclude_common_ancestry)
448
# Apply the other filters
449
return make_log_rev_iterator(branch, view_revisions,
451
files=specific_files,
455
def _format_diff(branch, rev, diff_type, files=None):
458
:param branch: Branch object
459
:param rev: Revision object
460
:param diff_type: Type of diff to generate
461
:param files: List of files to generate diff for (or None for all)
463
repo = branch.repository
464
if len(rev.parent_ids) == 0:
465
ancestor_id = _mod_revision.NULL_REVISION
467
ancestor_id = rev.parent_ids[0]
468
tree_1 = repo.revision_tree(ancestor_id)
469
tree_2 = repo.revision_tree(rev.revision_id)
470
if diff_type == 'partial' and files is not None:
471
specific_files = files
473
specific_files = None
475
path_encoding = get_diff_header_encoding()
476
diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
477
new_label='', path_encoding=path_encoding)
419
481
class _StartNotLinearAncestor(Exception):
423
485
class _DefaultLogGenerator(LogGenerator):
424
486
"""The default generator of log revisions."""
426
def __init__(self, branch, rqst):
489
self, branch, levels=None, limit=None, diff_type=None,
490
delta_type=None, show_signature=None, omit_merges=None,
491
generate_tags=None, specific_files=None, match=None,
492
start_revision=None, end_revision=None, direction=None,
493
exclude_common_ancestry=None, _match_using_deltas=None,
427
495
self.branch = branch
429
if rqst.get('generate_tags') and branch.supports_tags():
498
self.diff_type = diff_type
499
self.delta_type = delta_type
500
self.show_signature = signature
501
self.omit_merges = omit_merges
502
self.specific_files = specific_files
504
self.start_revision = start_revision
505
self.end_revision = end_revision
506
self.direction = direction
507
self.exclude_common_ancestry = exclude_common_ancestry
508
self._match_using_deltas = _match_using_deltas
509
if generate_tags and branch.supports_tags():
430
510
self.rev_tag_dict = branch.tags.get_reverse_tag_dict()
432
512
self.rev_tag_dict = {}
437
517
:return: An iterator yielding LogRevision objects.
440
levels = rqst.get('levels')
441
limit = rqst.get('limit')
442
diff_type = rqst.get('diff_type')
443
show_signature = rqst.get('signature')
444
omit_merges = rqst.get('omit_merges')
446
520
revision_iterator = self._create_log_revision_iterator()
447
521
for revs in revision_iterator:
448
522
for (rev_id, revno, merge_depth), rev, delta in revs:
449
523
# 0 levels means show everything; merge_depth counts from 0
450
if (levels != 0 and merge_depth is not None and
451
merge_depth >= levels):
524
if (self.levels != 0 and merge_depth is not None and
525
merge_depth >= self.levels):
453
if omit_merges and len(rev.parent_ids) > 1:
527
if self.omit_merges and len(rev.parent_ids) > 1:
456
530
raise errors.GhostRevisionUnusableHere(rev_id)
457
if diff_type is None:
531
if self.diff_type is None:
460
diff = self._format_diff(rev, rev_id, diff_type)
535
self.branch, rev, self.diff_type,
537
if self.show_signature:
462
538
signature = format_signature_validity(rev_id, self.branch)
465
541
yield LogRevision(
466
542
rev, revno, merge_depth, delta,
467
543
self.rev_tag_dict.get(rev_id), diff, signature)
470
if log_count >= limit:
546
if log_count >= self.limit:
473
def _format_diff(self, rev, rev_id, diff_type):
474
repo = self.branch.repository
475
if len(rev.parent_ids) == 0:
476
ancestor_id = _mod_revision.NULL_REVISION
478
ancestor_id = rev.parent_ids[0]
479
tree_1 = repo.revision_tree(ancestor_id)
480
tree_2 = repo.revision_tree(rev_id)
481
file_ids = self.rqst.get('specific_fileids')
482
if diff_type == 'partial' and file_ids is not None:
483
specific_files = [tree_2.id2path(id) for id in file_ids]
485
specific_files = None
487
path_encoding = get_diff_header_encoding()
488
diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
489
new_label='', path_encoding=path_encoding)
492
549
def _create_log_revision_iterator(self):
493
550
"""Create a revision iterator for log.
495
552
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
498
self.start_rev_id, self.end_rev_id = _get_revision_limits(
499
self.branch, self.rqst.get('start_revision'),
500
self.rqst.get('end_revision'))
501
if self.rqst.get('_match_using_deltas'):
502
return self._log_revision_iterator_using_delta_matching()
555
start_rev_id, end_rev_id = _get_revision_limits(
556
self.branch, self.start_revision, self.end_revision)
557
if self._match_using_deltas:
558
return _log_revision_iterator_using_delta_matching(
560
delta_type=self.delta_type,
563
specific_files=self.specific_files,
564
start_rev_id=start_rev_id, end_rev_id=end_rev_id,
565
direction=self.direction,
566
exclude_common_ancestry=self.exclude_common_ancestry,
504
569
# We're using the per-file-graph algorithm. This scales really
505
570
# well but only makes sense if there is a single file and it's
506
571
# not a directory
507
file_count = len(self.rqst.get('specific_fileids'))
572
file_count = len(self.specific_files)
508
573
if file_count != 1:
509
574
raise errors.BzrError(
510
575
"illegal LogRequest: must match-using-deltas "
511
576
"when logging %d files" % file_count)
512
return self._log_revision_iterator_using_per_file_graph()
514
def _log_revision_iterator_using_delta_matching(self):
515
# Get the base revisions, filtering by the revision range
517
generate_merge_revisions = rqst.get('levels') != 1
518
delayed_graph_generation = not rqst.get('specific_fileids') and (
519
rqst.get('limit') or self.start_rev_id or self.end_rev_id)
520
view_revisions = _calc_view_revisions(
521
self.branch, self.start_rev_id, self.end_rev_id,
522
rqst.get('direction'),
523
generate_merge_revisions=generate_merge_revisions,
524
delayed_graph_generation=delayed_graph_generation,
525
exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
527
# Apply the other filters
528
return make_log_rev_iterator(self.branch, view_revisions,
529
rqst.get('delta_type'), rqst.get('match'),
530
file_ids=rqst.get('specific_fileids'),
531
direction=rqst.get('direction'))
533
def _log_revision_iterator_using_per_file_graph(self):
534
# Get the base revisions, filtering by the revision range.
535
# Note that we always generate the merge revisions because
536
# filter_revisions_touching_file_id() requires them ...
538
view_revisions = _calc_view_revisions(
539
self.branch, self.start_rev_id, self.end_rev_id,
540
rqst.get('direction'), generate_merge_revisions=True,
541
exclude_common_ancestry=rqst.get('exclude_common_ancestry'))
542
if not isinstance(view_revisions, list):
543
view_revisions = list(view_revisions)
544
view_revisions = _filter_revisions_touching_file_id(self.branch,
545
rqst.get('specific_fileids')[
547
include_merges=rqst.get('levels') != 1)
548
return make_log_rev_iterator(self.branch, view_revisions,
549
rqst.get('delta_type'), rqst.get('match'))
577
return _log_revision_iterator_using_per_file_graph(
579
delta_type=self.delta_type,
582
path=self.specific_files[0],
583
start_rev_id=start_rev_id, end_rev_id=end_rev_id,
584
direction=self.direction,
585
exclude_common_ancestry=self.exclude_common_ancestry
552
589
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
941
978
:param search: A user text search string.
942
979
:param log_rev_iterator: An input iterator containing all revisions that
943
980
could be displayed, in lists.
944
:param fileids: If non empty, only revisions matching one or more of
945
the file-ids are to be kept.
981
:param files: If non empty, only revisions matching one or more of
982
the files are to be kept.
946
983
:param direction: the direction in which view_revisions is sorted
947
984
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
950
if not generate_delta and not fileids:
987
if not generate_delta and not files:
951
988
return log_rev_iterator
952
989
return _generate_deltas(branch.repository, log_rev_iterator,
953
generate_delta, fileids, direction)
956
def _generate_deltas(repository, log_rev_iterator, delta_type, fileids,
990
generate_delta, files, direction)
993
def _generate_deltas(repository, log_rev_iterator, delta_type, files,
958
995
"""Create deltas for each batch of revisions in log_rev_iterator.
960
997
If we're only generating deltas for the sake of filtering against
961
file-ids, we stop generating deltas once all file-ids reach the
998
files, we stop generating deltas once all files reach the
962
999
appropriate life-cycle point. If we're receiving data newest to
963
1000
oldest, then that life-cycle point is 'add', otherwise it's 'remove'.
965
check_fileids = fileids is not None and len(fileids) > 0
967
fileid_set = set(fileids)
1002
check_files = files is not None and len(files) > 0
1004
file_set = set(files)
968
1005
if direction == 'reverse':
971
1008
stop_on = 'remove'
974
1011
for revs in log_rev_iterator:
975
# If we were matching against fileids and we've run out,
1012
# If we were matching against files and we've run out,
976
1013
# there's nothing left to do
977
if check_fileids and not fileid_set:
1014
if check_files and not file_set:
979
1016
revisions = [rev[1] for rev in revs]
981
if delta_type == 'full' and not check_fileids:
982
deltas = repository.get_deltas_for_revisions(revisions)
1018
if delta_type == 'full' and not check_files:
1019
deltas = repository.get_revision_deltas(revisions)
983
1020
for rev, delta in zip(revs, deltas):
984
1021
new_revs.append((rev[0], rev[1], delta))
986
deltas = repository.get_deltas_for_revisions(revisions, fileid_set)
1023
deltas = repository.get_revision_deltas(
1024
revisions, specific_files=file_set)
987
1025
for rev, delta in zip(revs, deltas):
989
1027
if delta is None or not delta.has_changed():
992
_update_fileids(delta, fileid_set, stop_on)
1030
_update_files(delta, file_set, stop_on)
993
1031
if delta_type is None:
995
1033
elif delta_type == 'full':
1009
def _update_fileids(delta, fileids, stop_on):
1010
"""Update the set of file-ids to search based on file lifecycle events.
1047
def _update_files(delta, files, stop_on):
1048
"""Update the set of files to search based on file lifecycle events.
1012
:param fileids: a set of fileids to update
1013
:param stop_on: either 'add' or 'remove' - take file-ids out of the
1014
fileids set once their add or remove entry is detected respectively
1050
:param files: a set of files to update
1051
:param stop_on: either 'add' or 'remove' - take files out of the
1052
files set once their add or remove entry is detected respectively
1016
1054
if stop_on == 'add':
1017
for item in delta.added + delta.copied:
1018
if item.file_id in fileids:
1019
fileids.remove(item.file_id)
1055
for item in delta.added:
1056
if item.path[1] in files:
1057
files.remove(item.path[1])
1058
for item in delta.copied + delta.renamed:
1059
if item.path[1] in files:
1060
files.remove(item.path[1])
1061
files.add(item.path[0])
1062
if item.kind[1] == 'directory':
1063
for path in list(files):
1064
if is_inside(item.path[1], path):
1066
files.add(item.path[0] + path[len(item.path[1]):])
1020
1067
elif stop_on == 'delete':
1021
1068
for item in delta.removed:
1022
if item.file_id in fileids:
1023
fileids.remove(item.file_id)
1069
if item.path[0] in files:
1070
files.remove(item.path[0])
1071
for item in delta.copied + delta.renamed:
1072
if item.path[0] in files:
1073
files.remove(item.path[0])
1074
files.add(item.path[1])
1075
if item.kind[0] == 'directory':
1076
for path in list(files):
1077
if is_inside(item.path[0], path):
1079
files.add(item.path[1] + path[len(item.path[0]):])
1026
1082
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
2081
2135
tree = b.basis_tree()
2083
2137
for fp in relpaths:
2084
file_id = tree.path2id(fp)
2085
kind = _get_kind_for_file_id(tree, fp, file_id)
2138
kind = _get_kind_for_file(tree, fp)
2087
2140
# go back to when time began
2088
2141
if tree1 is None:
2090
2143
rev1 = b.get_rev_id(1)
2091
2144
except errors.NoSuchRevision:
2092
2145
# No history at all
2096
2148
tree1 = b.repository.revision_tree(rev1)
2098
file_id = tree1.path2id(fp)
2099
kind = _get_kind_for_file_id(tree1, fp, file_id)
2100
info_list.append((fp, file_id, kind))
2150
kind = _get_kind_for_file(tree1, fp)
2151
info_list.append((fp, kind))
2102
2153
elif start_rev_info == end_rev_info:
2103
2154
# One revision given - file must exist in it
2104
2155
tree = b.repository.revision_tree(end_rev_info.rev_id)
2105
2156
for fp in relpaths:
2106
file_id = tree.path2id(fp)
2107
kind = _get_kind_for_file_id(tree, fp, file_id)
2108
info_list.append((fp, file_id, kind))
2157
kind = _get_kind_for_file(tree, fp)
2158
info_list.append((fp, kind))
2111
2161
# Revision range given. Get the file-id from the end tree.