/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/log.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-07-20 02:17:05 UTC
  • mfrom: (7518.1.2 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200720021705-5f11tmo1hdqjxm6x
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/387628

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
* in "verbose" mode with a description of what changed from one
25
25
  version to the next
26
26
 
27
 
* with file-ids and revision-ids shown
 
27
* with files and revision-ids shown
28
28
 
29
29
Logs are actually written out through an abstract LogFormatter
30
30
interface, which allows for different preferred formats.  Plugins can
81
81
    format_date_with_offset_in_original_timezone,
82
82
    get_diff_header_encoding,
83
83
    get_terminal_encoding,
 
84
    is_inside,
84
85
    terminal_width,
85
86
    )
86
87
from .tree import (
90
91
 
91
92
 
92
93
def find_touching_revisions(repository, last_revision, last_tree, last_path):
93
 
    """Yield a description of revisions which affect the file_id.
 
94
    """Yield a description of revisions which affect the file.
94
95
 
95
96
    Each returned element is (revno, revision_id, description)
96
97
 
138
139
             direction='reverse',
139
140
             start_revision=None,
140
141
             end_revision=None,
141
 
             search=None,
142
142
             limit=None,
143
143
             show_diff=False,
144
144
             match=None):
160
160
 
161
161
    :param end_revision: If not None, only show revisions <= end_revision
162
162
 
163
 
    :param search: If not None, only show revisions with matching commit
164
 
        messages
165
 
 
166
163
    :param limit: If set, shows only 'limit' revisions, all revisions are shown
167
164
        if None or 0.
168
165
 
199
196
    rqst = make_log_request_dict(
200
197
        direction=direction,
201
198
        start_revision=start_revision, end_revision=end_revision,
202
 
        limit=limit, message_search=search,
203
 
        delta_type=delta_type, diff_type=diff_type)
 
199
        limit=limit, delta_type=delta_type, diff_type=diff_type)
204
200
    Logger(branch, rqst).show(lf)
205
201
 
206
202
 
215
211
    }
216
212
 
217
213
 
218
 
def make_log_request_dict(direction='reverse', specific_fileids=None,
 
214
def make_log_request_dict(direction='reverse', specific_files=None,
219
215
                          start_revision=None, end_revision=None, limit=None,
220
216
                          message_search=None, levels=None, generate_tags=True,
221
217
                          delta_type=None,
232
228
    :param direction: 'reverse' (default) is latest to earliest;
233
229
      'forward' is earliest to latest.
234
230
 
235
 
    :param specific_fileids: If not None, only include revisions
 
231
    :param specific_files: If not None, only include revisions
236
232
      affecting the specified files, rather than all revisions.
237
233
 
238
234
    :param start_revision: If not None, only generate
255
251
`
256
252
    :param delta_type: Either 'full', 'partial' or None.
257
253
      'full' means generate the complete delta - adds/deletes/modifies/etc;
258
 
      'partial' means filter the delta using specific_fileids;
 
254
      'partial' means filter the delta using specific_files;
259
255
      None means do not generate any delta.
260
256
 
261
257
    :param diff_type: Either 'full', 'partial' or None.
262
258
      'full' means generate the complete diff - adds/deletes/modifies/etc;
263
 
      'partial' means filter the diff using specific_fileids;
 
259
      'partial' means filter the diff using specific_files;
264
260
      None means do not generate any diff.
265
261
 
266
262
    :param _match_using_deltas: a private parameter controlling the
267
 
      algorithm used for matching specific_fileids. This parameter
 
263
      algorithm used for matching specific_files. This parameter
268
264
      may be removed in the future so breezy client code should NOT
269
265
      use it.
270
266
 
292
288
            match = {'message': [message_search]}
293
289
    return {
294
290
        'direction': direction,
295
 
        'specific_fileids': specific_fileids,
 
291
        'specific_files': specific_files,
296
292
        'start_revision': start_revision,
297
293
        'end_revision': end_revision,
298
294
        'limit': limit,
413
409
 
414
410
        Subclasses may wish to override this.
415
411
        """
416
 
        return _DefaultLogGenerator(branch, rqst)
 
412
        return _DefaultLogGenerator(branch, **rqst)
 
413
 
 
414
 
 
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)
 
432
 
 
433
 
 
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,
 
443
        direction,
 
444
        generate_merge_revisions=generate_merge_revisions,
 
445
        delayed_graph_generation=delayed_graph_generation,
 
446
        exclude_common_ancestry=exclude_common_ancestry)
 
447
 
 
448
    # Apply the other filters
 
449
    return make_log_rev_iterator(branch, view_revisions,
 
450
                                 delta_type, match,
 
451
                                 files=specific_files,
 
452
                                 direction=direction)
 
453
 
 
454
 
 
455
def _format_diff(branch, rev, diff_type, files=None):
 
456
    """Format a diff.
 
457
 
 
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)
 
462
    """
 
463
    repo = branch.repository
 
464
    if len(rev.parent_ids) == 0:
 
465
        ancestor_id = _mod_revision.NULL_REVISION
 
466
    else:
 
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
 
472
    else:
 
473
        specific_files = None
 
474
    s = BytesIO()
 
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)
 
478
    return s.getvalue()
417
479
 
418
480
 
419
481
class _StartNotLinearAncestor(Exception):
423
485
class _DefaultLogGenerator(LogGenerator):
424
486
    """The default generator of log revisions."""
425
487
 
426
 
    def __init__(self, branch, rqst):
 
488
    def __init__(
 
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,
 
494
            signature=None):
427
495
        self.branch = branch
428
 
        self.rqst = rqst
429
 
        if rqst.get('generate_tags') and branch.supports_tags():
 
496
        self.levels = levels
 
497
        self.limit = limit
 
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
 
503
        self.match = match
 
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()
431
511
        else:
432
512
            self.rev_tag_dict = {}
436
516
 
437
517
        :return: An iterator yielding LogRevision objects.
438
518
        """
439
 
        rqst = self.rqst
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')
445
519
        log_count = 0
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):
452
526
                    continue
453
 
                if omit_merges and len(rev.parent_ids) > 1:
 
527
                if self.omit_merges and len(rev.parent_ids) > 1:
454
528
                    continue
455
529
                if rev is None:
456
530
                    raise errors.GhostRevisionUnusableHere(rev_id)
457
 
                if diff_type is None:
 
531
                if self.diff_type is None:
458
532
                    diff = None
459
533
                else:
460
 
                    diff = self._format_diff(rev, rev_id, diff_type)
461
 
                if show_signature:
 
534
                    diff = _format_diff(
 
535
                        self.branch, rev, self.diff_type,
 
536
                        self.specific_files)
 
537
                if self.show_signature:
462
538
                    signature = format_signature_validity(rev_id, self.branch)
463
539
                else:
464
540
                    signature = None
465
541
                yield LogRevision(
466
542
                    rev, revno, merge_depth, delta,
467
543
                    self.rev_tag_dict.get(rev_id), diff, signature)
468
 
                if limit:
 
544
                if self.limit:
469
545
                    log_count += 1
470
 
                    if log_count >= limit:
 
546
                    if log_count >= self.limit:
471
547
                        return
472
548
 
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
477
 
        else:
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]
484
 
        else:
485
 
            specific_files = None
486
 
        s = BytesIO()
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)
490
 
        return s.getvalue()
491
 
 
492
549
    def _create_log_revision_iterator(self):
493
550
        """Create a revision iterator for log.
494
551
 
495
552
        :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
496
553
            delta).
497
554
        """
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(
 
559
                self.branch,
 
560
                delta_type=self.delta_type,
 
561
                match=self.match,
 
562
                levels=self.levels,
 
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,
 
567
                limit=self.limit)
503
568
        else:
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()
513
 
 
514
 
    def _log_revision_iterator_using_delta_matching(self):
515
 
        # Get the base revisions, filtering by the revision range
516
 
        rqst = self.rqst
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'))
526
 
 
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'))
532
 
 
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 ...
537
 
        rqst = self.rqst
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')[
546
 
                                                                0], view_revisions,
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(
 
578
                self.branch,
 
579
                delta_type=self.delta_type,
 
580
                match=self.match,
 
581
                levels=self.levels,
 
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
 
586
                )
550
587
 
551
588
 
552
589
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
844
881
 
845
882
 
846
883
def make_log_rev_iterator(branch, view_revisions, generate_delta, search,
847
 
                          file_ids=None, direction='reverse'):
 
884
                          files=None, direction='reverse'):
848
885
    """Create a revision iterator for log.
849
886
 
850
887
    :param branch: The branch being logged.
852
889
    :param generate_delta: Whether to generate a delta for each revision.
853
890
      Permitted values are None, 'full' and 'partial'.
854
891
    :param search: A user text search string.
855
 
    :param file_ids: If non empty, only revisions matching one or more of
856
 
      the file-ids are to be kept.
 
892
    :param files: If non empty, only revisions matching one or more of
 
893
      the files are to be kept.
857
894
    :param direction: the direction in which view_revisions is sorted
858
895
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
859
896
        delta).
875
912
        # with custom parameters. This will do for now. IGC 20090127
876
913
        if adapter == _make_delta_filter:
877
914
            log_rev_iterator = adapter(
878
 
                branch, generate_delta, search, log_rev_iterator, file_ids,
 
915
                branch, generate_delta, search, log_rev_iterator, files,
879
916
                direction)
880
917
        else:
881
918
            log_rev_iterator = adapter(
932
969
 
933
970
 
934
971
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
935
 
                       fileids=None, direction='reverse'):
 
972
                       files=None, direction='reverse'):
936
973
    """Add revision deltas to a log iterator if needed.
937
974
 
938
975
    :param branch: The branch being logged.
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,
948
985
        delta).
949
986
    """
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)
954
 
 
955
 
 
956
 
def _generate_deltas(repository, log_rev_iterator, delta_type, fileids,
 
990
                            generate_delta, files, direction)
 
991
 
 
992
 
 
993
def _generate_deltas(repository, log_rev_iterator, delta_type, files,
957
994
                     direction):
958
995
    """Create deltas for each batch of revisions in log_rev_iterator.
959
996
 
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'.
964
1001
    """
965
 
    check_fileids = fileids is not None and len(fileids) > 0
966
 
    if check_fileids:
967
 
        fileid_set = set(fileids)
 
1002
    check_files = files is not None and len(files) > 0
 
1003
    if check_files:
 
1004
        file_set = set(files)
968
1005
        if direction == 'reverse':
969
1006
            stop_on = 'add'
970
1007
        else:
971
1008
            stop_on = 'remove'
972
1009
    else:
973
 
        fileid_set = None
 
1010
        file_set = None
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:
978
1015
            return
979
1016
        revisions = [rev[1] for rev in revs]
980
1017
        new_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))
985
1022
        else:
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):
988
 
                if check_fileids:
 
1026
                if check_files:
989
1027
                    if delta is None or not delta.has_changed():
990
1028
                        continue
991
1029
                    else:
992
 
                        _update_fileids(delta, fileid_set, stop_on)
 
1030
                        _update_files(delta, file_set, stop_on)
993
1031
                        if delta_type is None:
994
1032
                            delta = None
995
1033
                        elif delta_type == 'full':
1006
1044
        yield new_revs
1007
1045
 
1008
1046
 
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.
1011
1049
 
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
1015
1053
    """
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):
 
1065
                        files.remove(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):
 
1078
                        files.remove(path)
 
1079
                        files.add(item.path[1] + path[len(item.path[0]):])
1024
1080
 
1025
1081
 
1026
1082
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
1067
1123
def _get_revision_limits(branch, start_revision, end_revision):
1068
1124
    """Get and check revision limits.
1069
1125
 
1070
 
    :param  branch: The branch containing the revisions.
1071
 
 
1072
 
    :param  start_revision: The first revision to be logged.
1073
 
            but for merge revision support a RevisionInfo is expected.
1074
 
 
1075
 
    :param  end_revision: The last revision to be logged.
1076
 
            For backwards compatibility this may be a mainline integer revno,
1077
 
            but for merge revision support a RevisionInfo is expected.
 
1126
    :param branch: The branch containing the revisions.
 
1127
 
 
1128
    :param start_revision: The first revision to be logged, as a RevisionInfo.
 
1129
 
 
1130
    :param end_revision: The last revision to be logged, as a RevisionInfo
1078
1131
 
1079
1132
    :return: (start_rev_id, end_rev_id) tuple.
1080
1133
    """
1190
1243
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1191
1244
 
1192
1245
 
1193
 
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1194
 
                                       include_merges=True):
1195
 
    r"""Return the list of revision ids which touch a given file id.
 
1246
def _filter_revisions_touching_path(branch, path, view_revisions,
 
1247
                                    include_merges=True):
 
1248
    r"""Return the list of revision ids which touch a given path.
1196
1249
 
1197
1250
    The function filters view_revisions and returns a subset.
1198
 
    This includes the revisions which directly change the file id,
 
1251
    This includes the revisions which directly change the path,
1199
1252
    and the revisions which merge these changes. So if the
1200
1253
    revision graph is::
1201
1254
 
1218
1271
 
1219
1272
    :param branch: The branch where we can get text revision information.
1220
1273
 
1221
 
    :param file_id: Filter out revisions that do not touch file_id.
 
1274
    :param path: Filter out revisions that do not touch path.
1222
1275
 
1223
1276
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
1224
1277
        tuples. This is the list of revisions which will be filtered. It is
1232
1285
    # Lookup all possible text keys to determine which ones actually modified
1233
1286
    # the file.
1234
1287
    graph = branch.repository.get_file_graph()
 
1288
    start_tree = branch.repository.revision_tree(view_revisions[0][0])
 
1289
    file_id = start_tree.path2id(path)
1235
1290
    get_parent_map = graph.get_parent_map
1236
1291
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1237
1292
    next_keys = None
1939
1994
                 verbose=False,
1940
1995
                 direction='forward',
1941
1996
                 start_revision=base_idx + 1,
1942
 
                 end_revision=len(new_rh),
1943
 
                 search=None)
 
1997
                 end_revision=len(new_rh))
1944
1998
 
1945
1999
 
1946
2000
def get_history_change(old_revision_id, new_revision_id, repository):
2039
2093
 
2040
2094
 
2041
2095
def _get_info_for_log_files(revisionspec_list, file_list, exit_stack):
2042
 
    """Find file-ids and kinds given a list of files and a revision range.
 
2096
    """Find files and kinds given a list of files and a revision range.
2043
2097
 
2044
2098
    We search for files at the end of the range. If not found there,
2045
2099
    we try the start of the range.
2051
2105
    :param exit_stack: When the branch returned is read locked,
2052
2106
      an unlock call will be queued to the exit stack.
2053
2107
    :return: (branch, info_list, start_rev_info, end_rev_info) where
2054
 
      info_list is a list of (relative_path, file_id, kind) tuples where
 
2108
      info_list is a list of (relative_path, found, kind) tuples where
2055
2109
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2056
2110
      branch will be read-locked.
2057
2111
    """
2081
2135
            tree = b.basis_tree()
2082
2136
        tree1 = None
2083
2137
        for fp in relpaths:
2084
 
            file_id = tree.path2id(fp)
2085
 
            kind = _get_kind_for_file_id(tree, fp, file_id)
2086
 
            if file_id is None:
 
2138
            kind = _get_kind_for_file(tree, fp)
 
2139
            if not kind:
2087
2140
                # go back to when time began
2088
2141
                if tree1 is None:
2089
2142
                    try:
2090
2143
                        rev1 = b.get_rev_id(1)
2091
2144
                    except errors.NoSuchRevision:
2092
2145
                        # No history at all
2093
 
                        file_id = None
2094
2146
                        kind = None
2095
2147
                    else:
2096
2148
                        tree1 = b.repository.revision_tree(rev1)
2097
2149
                if tree1:
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))
2101
2152
 
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))
2109
2159
 
2110
2160
    else:
2111
2161
        # Revision range given. Get the file-id from the end tree.
2117
2167
            tree = b.repository.revision_tree(rev_id)
2118
2168
        tree1 = None
2119
2169
        for fp in relpaths:
2120
 
            file_id = tree.path2id(fp)
2121
 
            kind = _get_kind_for_file_id(tree, fp, file_id)
2122
 
            if file_id is None:
 
2170
            kind = _get_kind_for_file(tree, fp)
 
2171
            if not kind:
2123
2172
                if tree1 is None:
2124
2173
                    rev_id = start_rev_info.rev_id
2125
2174
                    if rev_id is None:
2127
2176
                        tree1 = b.repository.revision_tree(rev1)
2128
2177
                    else:
2129
2178
                        tree1 = b.repository.revision_tree(rev_id)
2130
 
                file_id = tree1.path2id(fp)
2131
 
                kind = _get_kind_for_file_id(tree1, fp, file_id)
2132
 
            info_list.append((fp, file_id, kind))
 
2179
                kind = _get_kind_for_file(tree1, fp)
 
2180
            info_list.append((fp, kind))
2133
2181
    return b, info_list, start_rev_info, end_rev_info
2134
2182
 
2135
2183
 
2136
 
def _get_kind_for_file_id(tree, path, file_id):
2137
 
    """Return the kind of a file-id or None if it doesn't exist."""
2138
 
    if file_id is not None:
2139
 
        return tree.kind(path)
2140
 
    else:
2141
 
        return None
 
2184
def _get_kind_for_file(tree, path):
 
2185
    """Return the kind of a path or None if it doesn't exist."""
 
2186
    with tree.lock_read():
 
2187
        try:
 
2188
            return tree.stored_kind(path)
 
2189
        except errors.NoSuchFile:
 
2190
            return None
2142
2191
 
2143
2192
 
2144
2193
properties_handler_registry = registry.Registry()