/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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