/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: Jelmer Vernooij
  • Date: 2020-02-21 03:58:42 UTC
  • mfrom: (7490.3.4 work)
  • mto: This revision was merged to the branch mainline in revision 7495.
  • Revision ID: jelmer@jelmer.uk-20200221035842-j97r6b74q8cgxb21
merge lp:brz/3.1.

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 files and revision-ids shown
 
27
* with file-ids 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,
85
84
    terminal_width,
86
85
    )
87
86
from .tree import (
91
90
 
92
91
 
93
92
def find_touching_revisions(repository, last_revision, last_tree, last_path):
94
 
    """Yield a description of revisions which affect the file.
 
93
    """Yield a description of revisions which affect the file_id.
95
94
 
96
95
    Each returned element is (revno, revision_id, description)
97
96
 
139
138
             direction='reverse',
140
139
             start_revision=None,
141
140
             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
 
163
166
    :param limit: If set, shows only 'limit' revisions, all revisions are shown
164
167
        if None or 0.
165
168
 
196
199
    rqst = make_log_request_dict(
197
200
        direction=direction,
198
201
        start_revision=start_revision, end_revision=end_revision,
199
 
        limit=limit, delta_type=delta_type, diff_type=diff_type)
 
202
        limit=limit, message_search=search,
 
203
        delta_type=delta_type, diff_type=diff_type)
200
204
    Logger(branch, rqst).show(lf)
201
205
 
202
206
 
211
215
    }
212
216
 
213
217
 
214
 
def make_log_request_dict(direction='reverse', specific_files=None,
 
218
def make_log_request_dict(direction='reverse', specific_fileids=None,
215
219
                          start_revision=None, end_revision=None, limit=None,
216
220
                          message_search=None, levels=None, generate_tags=True,
217
221
                          delta_type=None,
228
232
    :param direction: 'reverse' (default) is latest to earliest;
229
233
      'forward' is earliest to latest.
230
234
 
231
 
    :param specific_files: If not None, only include revisions
 
235
    :param specific_fileids: If not None, only include revisions
232
236
      affecting the specified files, rather than all revisions.
233
237
 
234
238
    :param start_revision: If not None, only generate
251
255
`
252
256
    :param delta_type: Either 'full', 'partial' or None.
253
257
      'full' means generate the complete delta - adds/deletes/modifies/etc;
254
 
      'partial' means filter the delta using specific_files;
 
258
      'partial' means filter the delta using specific_fileids;
255
259
      None means do not generate any delta.
256
260
 
257
261
    :param diff_type: Either 'full', 'partial' or None.
258
262
      'full' means generate the complete diff - adds/deletes/modifies/etc;
259
 
      'partial' means filter the diff using specific_files;
 
263
      'partial' means filter the diff using specific_fileids;
260
264
      None means do not generate any diff.
261
265
 
262
266
    :param _match_using_deltas: a private parameter controlling the
263
 
      algorithm used for matching specific_files. This parameter
 
267
      algorithm used for matching specific_fileids. This parameter
264
268
      may be removed in the future so breezy client code should NOT
265
269
      use it.
266
270
 
288
292
            match = {'message': [message_search]}
289
293
    return {
290
294
        'direction': direction,
291
 
        'specific_files': specific_files,
 
295
        'specific_fileids': specific_fileids,
292
296
        'start_revision': start_revision,
293
297
        'end_revision': end_revision,
294
298
        'limit': limit,
400
404
            for lr in generator.iter_log_revisions():
401
405
                lf.log_revision(lr)
402
406
        except errors.GhostRevisionUnusableHere:
403
 
            raise errors.CommandError(
 
407
            raise errors.BzrCommandError(
404
408
                gettext('Further revision history missing.'))
405
409
        lf.show_advice()
406
410
 
409
413
 
410
414
        Subclasses may wish to override this.
411
415
        """
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()
 
416
        return _DefaultLogGenerator(branch, rqst)
479
417
 
480
418
 
481
419
class _StartNotLinearAncestor(Exception):
485
423
class _DefaultLogGenerator(LogGenerator):
486
424
    """The default generator of log revisions."""
487
425
 
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):
 
426
    def __init__(self, branch, rqst):
495
427
        self.branch = branch
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():
 
428
        self.rqst = rqst
 
429
        if rqst.get('generate_tags') and branch.supports_tags():
510
430
            self.rev_tag_dict = branch.tags.get_reverse_tag_dict()
511
431
        else:
512
432
            self.rev_tag_dict = {}
516
436
 
517
437
        :return: An iterator yielding LogRevision objects.
518
438
        """
 
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')
519
445
        log_count = 0
520
446
        revision_iterator = self._create_log_revision_iterator()
521
447
        for revs in revision_iterator:
522
448
            for (rev_id, revno, merge_depth), rev, delta in revs:
523
449
                # 0 levels means show everything; merge_depth counts from 0
524
 
                if (self.levels != 0 and merge_depth is not None and
525
 
                        merge_depth >= self.levels):
 
450
                if (levels != 0 and merge_depth is not None and
 
451
                        merge_depth >= levels):
526
452
                    continue
527
 
                if self.omit_merges and len(rev.parent_ids) > 1:
 
453
                if omit_merges and len(rev.parent_ids) > 1:
528
454
                    continue
529
455
                if rev is None:
530
456
                    raise errors.GhostRevisionUnusableHere(rev_id)
531
 
                if self.diff_type is None:
 
457
                if diff_type is None:
532
458
                    diff = None
533
459
                else:
534
 
                    diff = _format_diff(
535
 
                        self.branch, rev, self.diff_type,
536
 
                        self.specific_files)
537
 
                if self.show_signature:
 
460
                    diff = self._format_diff(rev, rev_id, diff_type)
 
461
                if show_signature:
538
462
                    signature = format_signature_validity(rev_id, self.branch)
539
463
                else:
540
464
                    signature = None
541
465
                yield LogRevision(
542
466
                    rev, revno, merge_depth, delta,
543
467
                    self.rev_tag_dict.get(rev_id), diff, signature)
544
 
                if self.limit:
 
468
                if limit:
545
469
                    log_count += 1
546
 
                    if log_count >= self.limit:
 
470
                    if log_count >= limit:
547
471
                        return
548
472
 
 
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
 
549
492
    def _create_log_revision_iterator(self):
550
493
        """Create a revision iterator for log.
551
494
 
552
495
        :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
553
496
            delta).
554
497
        """
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)
 
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()
568
503
        else:
569
504
            # We're using the per-file-graph algorithm. This scales really
570
505
            # well but only makes sense if there is a single file and it's
571
506
            # not a directory
572
 
            file_count = len(self.specific_files)
 
507
            file_count = len(self.rqst.get('specific_fileids'))
573
508
            if file_count != 1:
574
509
                raise errors.BzrError(
575
510
                    "illegal LogRequest: must match-using-deltas "
576
511
                    "when logging %d files" % file_count)
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
 
                )
 
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'))
587
550
 
588
551
 
589
552
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
597
560
             a list of the same tuples.
598
561
    """
599
562
    if (exclude_common_ancestry and start_rev_id == end_rev_id):
600
 
        raise errors.CommandError(gettext(
 
563
        raise errors.BzrCommandError(gettext(
601
564
            '--exclude-common-ancestry requires two different revisions'))
602
565
    if direction not in ('reverse', 'forward'):
603
566
        raise ValueError(gettext('invalid direction %r') % direction)
688
651
        except _StartNotLinearAncestor:
689
652
            # A merge was never detected so the lower revision limit can't
690
653
            # be nested down somewhere
691
 
            raise errors.CommandError(gettext('Start revision not found in'
 
654
            raise errors.BzrCommandError(gettext('Start revision not found in'
692
655
                                                 ' history of end revision.'))
693
656
 
694
657
    # We exit the loop above because we encounter a revision with merges, from
881
844
 
882
845
 
883
846
def make_log_rev_iterator(branch, view_revisions, generate_delta, search,
884
 
                          files=None, direction='reverse'):
 
847
                          file_ids=None, direction='reverse'):
885
848
    """Create a revision iterator for log.
886
849
 
887
850
    :param branch: The branch being logged.
889
852
    :param generate_delta: Whether to generate a delta for each revision.
890
853
      Permitted values are None, 'full' and 'partial'.
891
854
    :param search: A user text search string.
892
 
    :param files: If non empty, only revisions matching one or more of
893
 
      the files are to be kept.
 
855
    :param file_ids: If non empty, only revisions matching one or more of
 
856
      the file-ids are to be kept.
894
857
    :param direction: the direction in which view_revisions is sorted
895
858
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
896
859
        delta).
912
875
        # with custom parameters. This will do for now. IGC 20090127
913
876
        if adapter == _make_delta_filter:
914
877
            log_rev_iterator = adapter(
915
 
                branch, generate_delta, search, log_rev_iterator, files,
 
878
                branch, generate_delta, search, log_rev_iterator, file_ids,
916
879
                direction)
917
880
        else:
918
881
            log_rev_iterator = adapter(
969
932
 
970
933
 
971
934
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
972
 
                       files=None, direction='reverse'):
 
935
                       fileids=None, direction='reverse'):
973
936
    """Add revision deltas to a log iterator if needed.
974
937
 
975
938
    :param branch: The branch being logged.
978
941
    :param search: A user text search string.
979
942
    :param log_rev_iterator: An input iterator containing all revisions that
980
943
        could be displayed, in lists.
981
 
    :param files: If non empty, only revisions matching one or more of
982
 
      the files are to be kept.
 
944
    :param fileids: If non empty, only revisions matching one or more of
 
945
      the file-ids are to be kept.
983
946
    :param direction: the direction in which view_revisions is sorted
984
947
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
985
948
        delta).
986
949
    """
987
 
    if not generate_delta and not files:
 
950
    if not generate_delta and not fileids:
988
951
        return log_rev_iterator
989
952
    return _generate_deltas(branch.repository, log_rev_iterator,
990
 
                            generate_delta, files, direction)
991
 
 
992
 
 
993
 
def _generate_deltas(repository, log_rev_iterator, delta_type, files,
 
953
                            generate_delta, fileids, direction)
 
954
 
 
955
 
 
956
def _generate_deltas(repository, log_rev_iterator, delta_type, fileids,
994
957
                     direction):
995
958
    """Create deltas for each batch of revisions in log_rev_iterator.
996
959
 
997
960
    If we're only generating deltas for the sake of filtering against
998
 
    files, we stop generating deltas once all files reach the
 
961
    file-ids, we stop generating deltas once all file-ids reach the
999
962
    appropriate life-cycle point. If we're receiving data newest to
1000
963
    oldest, then that life-cycle point is 'add', otherwise it's 'remove'.
1001
964
    """
1002
 
    check_files = files is not None and len(files) > 0
1003
 
    if check_files:
1004
 
        file_set = set(files)
 
965
    check_fileids = fileids is not None and len(fileids) > 0
 
966
    if check_fileids:
 
967
        fileid_set = set(fileids)
1005
968
        if direction == 'reverse':
1006
969
            stop_on = 'add'
1007
970
        else:
1008
971
            stop_on = 'remove'
1009
972
    else:
1010
 
        file_set = None
 
973
        fileid_set = None
1011
974
    for revs in log_rev_iterator:
1012
 
        # If we were matching against files and we've run out,
 
975
        # If we were matching against fileids and we've run out,
1013
976
        # there's nothing left to do
1014
 
        if check_files and not file_set:
 
977
        if check_fileids and not fileid_set:
1015
978
            return
1016
979
        revisions = [rev[1] for rev in revs]
1017
980
        new_revs = []
1018
 
        if delta_type == 'full' and not check_files:
1019
 
            deltas = repository.get_revision_deltas(revisions)
 
981
        if delta_type == 'full' and not check_fileids:
 
982
            deltas = repository.get_deltas_for_revisions(revisions)
1020
983
            for rev, delta in zip(revs, deltas):
1021
984
                new_revs.append((rev[0], rev[1], delta))
1022
985
        else:
1023
 
            deltas = repository.get_revision_deltas(
1024
 
                revisions, specific_files=file_set)
 
986
            deltas = repository.get_deltas_for_revisions(revisions, fileid_set)
1025
987
            for rev, delta in zip(revs, deltas):
1026
 
                if check_files:
 
988
                if check_fileids:
1027
989
                    if delta is None or not delta.has_changed():
1028
990
                        continue
1029
991
                    else:
1030
 
                        _update_files(delta, file_set, stop_on)
 
992
                        _update_fileids(delta, fileid_set, stop_on)
1031
993
                        if delta_type is None:
1032
994
                            delta = None
1033
995
                        elif delta_type == 'full':
1044
1006
        yield new_revs
1045
1007
 
1046
1008
 
1047
 
def _update_files(delta, files, stop_on):
1048
 
    """Update the set of files to search based on file lifecycle events.
 
1009
def _update_fileids(delta, fileids, stop_on):
 
1010
    """Update the set of file-ids to search based on file lifecycle events.
1049
1011
 
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
 
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
1053
1015
    """
1054
1016
    if stop_on == 'add':
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]):])
 
1017
        for item in delta.added + delta.copied:
 
1018
            if item.file_id in fileids:
 
1019
                fileids.remove(item.file_id)
1067
1020
    elif stop_on == 'delete':
1068
1021
        for item in delta.removed:
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]):])
 
1022
            if item.file_id in fileids:
 
1023
                fileids.remove(item.file_id)
1080
1024
 
1081
1025
 
1082
1026
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
1123
1067
def _get_revision_limits(branch, start_revision, end_revision):
1124
1068
    """Get and check revision limits.
1125
1069
 
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
 
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.
1131
1078
 
1132
1079
    :return: (start_rev_id, end_rev_id) tuple.
1133
1080
    """
1152
1099
    if branch.last_revision() != _mod_revision.NULL_REVISION:
1153
1100
        if (start_rev_id == _mod_revision.NULL_REVISION
1154
1101
                or end_rev_id == _mod_revision.NULL_REVISION):
1155
 
            raise errors.CommandError(
 
1102
            raise errors.BzrCommandError(
1156
1103
                gettext('Logging revision 0 is invalid.'))
1157
1104
        if end_revno is not None and start_revno > end_revno:
1158
 
            raise errors.CommandError(
 
1105
            raise errors.BzrCommandError(
1159
1106
                gettext("Start revision must be older than the end revision."))
1160
1107
    return (start_rev_id, end_rev_id)
1161
1108
 
1211
1158
 
1212
1159
    if ((start_rev_id == _mod_revision.NULL_REVISION)
1213
1160
            or (end_rev_id == _mod_revision.NULL_REVISION)):
1214
 
        raise errors.CommandError(gettext('Logging revision 0 is invalid.'))
 
1161
        raise errors.BzrCommandError(gettext('Logging revision 0 is invalid.'))
1215
1162
    if start_revno > end_revno:
1216
 
        raise errors.CommandError(gettext("Start revision must be older "
 
1163
        raise errors.BzrCommandError(gettext("Start revision must be older "
1217
1164
                                             "than the end revision."))
1218
1165
 
1219
1166
    if end_revno < start_revno:
1243
1190
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1244
1191
 
1245
1192
 
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.
 
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.
1249
1196
 
1250
1197
    The function filters view_revisions and returns a subset.
1251
 
    This includes the revisions which directly change the path,
 
1198
    This includes the revisions which directly change the file id,
1252
1199
    and the revisions which merge these changes. So if the
1253
1200
    revision graph is::
1254
1201
 
1271
1218
 
1272
1219
    :param branch: The branch where we can get text revision information.
1273
1220
 
1274
 
    :param path: Filter out revisions that do not touch path.
 
1221
    :param file_id: Filter out revisions that do not touch file_id.
1275
1222
 
1276
1223
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
1277
1224
        tuples. This is the list of revisions which will be filtered. It is
1285
1232
    # Lookup all possible text keys to determine which ones actually modified
1286
1233
    # the file.
1287
1234
    graph = branch.repository.get_file_graph()
1288
 
    start_tree = branch.repository.revision_tree(view_revisions[0][0])
1289
 
    file_id = start_tree.path2id(path)
1290
1235
    get_parent_map = graph.get_parent_map
1291
1236
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
1292
1237
    next_keys = None
1913
1858
    try:
1914
1859
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
1915
1860
    except KeyError:
1916
 
        raise errors.CommandError(
 
1861
        raise errors.BzrCommandError(
1917
1862
            gettext("unknown log formatter: %r") % name)
1918
1863
 
1919
1864
 
1994
1939
                 verbose=False,
1995
1940
                 direction='forward',
1996
1941
                 start_revision=base_idx + 1,
1997
 
                 end_revision=len(new_rh))
 
1942
                 end_revision=len(new_rh),
 
1943
                 search=None)
1998
1944
 
1999
1945
 
2000
1946
def get_history_change(old_revision_id, new_revision_id, repository):
2093
2039
 
2094
2040
 
2095
2041
def _get_info_for_log_files(revisionspec_list, file_list, exit_stack):
2096
 
    """Find files and kinds given a list of files and a revision range.
 
2042
    """Find file-ids and kinds given a list of files and a revision range.
2097
2043
 
2098
2044
    We search for files at the end of the range. If not found there,
2099
2045
    we try the start of the range.
2105
2051
    :param exit_stack: When the branch returned is read locked,
2106
2052
      an unlock call will be queued to the exit stack.
2107
2053
    :return: (branch, info_list, start_rev_info, end_rev_info) where
2108
 
      info_list is a list of (relative_path, found, kind) tuples where
 
2054
      info_list is a list of (relative_path, file_id, kind) tuples where
2109
2055
      kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2110
2056
      branch will be read-locked.
2111
2057
    """
2135
2081
            tree = b.basis_tree()
2136
2082
        tree1 = None
2137
2083
        for fp in relpaths:
2138
 
            kind = _get_kind_for_file(tree, fp)
2139
 
            if not kind:
 
2084
            file_id = tree.path2id(fp)
 
2085
            kind = _get_kind_for_file_id(tree, fp, file_id)
 
2086
            if file_id is None:
2140
2087
                # go back to when time began
2141
2088
                if tree1 is None:
2142
2089
                    try:
2143
2090
                        rev1 = b.get_rev_id(1)
2144
2091
                    except errors.NoSuchRevision:
2145
2092
                        # No history at all
 
2093
                        file_id = None
2146
2094
                        kind = None
2147
2095
                    else:
2148
2096
                        tree1 = b.repository.revision_tree(rev1)
2149
2097
                if tree1:
2150
 
                    kind = _get_kind_for_file(tree1, fp)
2151
 
            info_list.append((fp, kind))
 
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))
2152
2101
 
2153
2102
    elif start_rev_info == end_rev_info:
2154
2103
        # One revision given - file must exist in it
2155
2104
        tree = b.repository.revision_tree(end_rev_info.rev_id)
2156
2105
        for fp in relpaths:
2157
 
            kind = _get_kind_for_file(tree, fp)
2158
 
            info_list.append((fp, kind))
 
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))
2159
2109
 
2160
2110
    else:
2161
2111
        # Revision range given. Get the file-id from the end tree.
2167
2117
            tree = b.repository.revision_tree(rev_id)
2168
2118
        tree1 = None
2169
2119
        for fp in relpaths:
2170
 
            kind = _get_kind_for_file(tree, fp)
2171
 
            if not kind:
 
2120
            file_id = tree.path2id(fp)
 
2121
            kind = _get_kind_for_file_id(tree, fp, file_id)
 
2122
            if file_id is None:
2172
2123
                if tree1 is None:
2173
2124
                    rev_id = start_rev_info.rev_id
2174
2125
                    if rev_id is None:
2176
2127
                        tree1 = b.repository.revision_tree(rev1)
2177
2128
                    else:
2178
2129
                        tree1 = b.repository.revision_tree(rev_id)
2179
 
                kind = _get_kind_for_file(tree1, fp)
2180
 
            info_list.append((fp, kind))
 
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))
2181
2133
    return b, info_list, start_rev_info, end_rev_info
2182
2134
 
2183
2135
 
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
 
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
2191
2142
 
2192
2143
 
2193
2144
properties_handler_registry = registry.Registry()