/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-13 23:57:28 UTC
  • mfrom: (7490 work)
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200213235728-m6ds0mm3mbs4y182
Merge trunk.

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