~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-02-03 10:06:19 UTC
  • mfrom: (4999.3.2 apport)
  • Revision ID: pqm@pqm.ubuntu.com-20100203100619-f76bo5y5bd5c14wk
(mbp) use apport to send bugs, not just store them

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
from cStringIO import StringIO
19
19
 
20
20
from bzrlib import (
21
 
    branchbuilder,
22
21
    errors,
23
22
    log,
24
23
    registry,
25
24
    revision,
26
25
    revisionspec,
 
26
    symbol_versioning,
27
27
    tests,
28
28
    )
29
29
 
116
116
            branch.tags.set_tag('v1.0', 'rev-3')
117
117
        return wt
118
118
 
119
 
 
120
119
class LogCatcher(log.LogFormatter):
121
120
    """Pull log messages into a list rather than displaying them.
122
121
 
250
249
        wt.commit(message='add file1 and file2')
251
250
        self.run_bzr('branch parent child')
252
251
        os.unlink('child/file1')
253
 
        with file('child/file2', 'wb') as f: f.write('hello\n')
 
252
        file('child/file2', 'wb').write('hello\n')
254
253
        self.run_bzr(['commit', '-m', 'remove file1 and modify file2',
255
254
            'child'])
256
255
        os.chdir('parent')
322
321
    1 Joe Foo\t2005-11-22
323
322
      rev-1
324
323
 
325
 
Use --include-merged or -n0 to see merged revisions.
 
324
Use --include-merges or -n0 to see merged revisions.
326
325
""",
327
326
            wt.branch, log.ShortLogFormatter,
328
327
            formatter_kwargs=dict(show_advice=True))
371
370
            wt.branch, log.ShortLogFormatter,
372
371
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
373
372
 
374
 
    def test_show_ids(self):
375
 
        wt = self.make_branch_and_tree('parent')
376
 
        self.build_tree(['parent/f1', 'parent/f2'])
377
 
        wt.add(['f1','f2'])
378
 
        self.wt_commit(wt, 'first post', rev_id='a')
379
 
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
380
 
        self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
381
 
        wt.merge_from_branch(child_wt.branch)
382
 
        self.wt_commit(wt, 'merge branch 1', rev_id='c')
383
 
        self.assertFormatterResult("""\
384
 
    2 Joe Foo\t2005-11-22 [merge]
385
 
      revision-id:c
386
 
      merge branch 1
387
 
 
388
 
          1.1.1 Joe Foo\t2005-11-22
389
 
                revision-id:b
390
 
                branch 1 changes
391
 
 
392
 
    1 Joe Foo\t2005-11-22
393
 
      revision-id:a
394
 
      first post
395
 
 
396
 
""",
397
 
            wt.branch, log.ShortLogFormatter,
398
 
            formatter_kwargs=dict(levels=0,show_ids=True))
399
 
 
400
373
 
401
374
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
402
375
 
685
658
        self.assertEqualDiff('''custom_prop_name: test_value\n''',
686
659
                             sio.getvalue())
687
660
 
688
 
    def test_show_ids(self):
689
 
        wt = self.make_branch_and_tree('parent')
690
 
        self.build_tree(['parent/f1', 'parent/f2'])
691
 
        wt.add(['f1','f2'])
692
 
        self.wt_commit(wt, 'first post', rev_id='a')
693
 
        child_wt = wt.bzrdir.sprout('child').open_workingtree()
694
 
        self.wt_commit(child_wt, 'branch 1 changes', rev_id='b')
695
 
        wt.merge_from_branch(child_wt.branch)
696
 
        self.wt_commit(wt, 'merge branch 1', rev_id='c')
697
 
        self.assertFormatterResult("""\
698
 
------------------------------------------------------------
699
 
revno: 2 [merge]
700
 
revision-id: c
701
 
parent: a
702
 
parent: b
703
 
committer: Joe Foo <joe@foo.com>
704
 
branch nick: parent
705
 
timestamp: Tue 2005-11-22 00:00:02 +0000
706
 
message:
707
 
  merge branch 1
708
 
    ------------------------------------------------------------
709
 
    revno: 1.1.1
710
 
    revision-id: b
711
 
    parent: a
712
 
    committer: Joe Foo <joe@foo.com>
713
 
    branch nick: child
714
 
    timestamp: Tue 2005-11-22 00:00:01 +0000
715
 
    message:
716
 
      branch 1 changes
717
 
------------------------------------------------------------
718
 
revno: 1
719
 
revision-id: a
720
 
committer: Joe Foo <joe@foo.com>
721
 
branch nick: parent
722
 
timestamp: Tue 2005-11-22 00:00:00 +0000
723
 
message:
724
 
  first post
725
 
""",
726
 
            wt.branch, log.LongLogFormatter,
727
 
            formatter_kwargs=dict(levels=0,show_ids=True))
728
 
 
729
661
 
730
662
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
731
663
 
945
877
            formatter_kwargs=dict(levels=0))
946
878
 
947
879
 
948
 
class TestGnuChangelogFormatter(TestCaseForLogFormatter):
949
 
 
950
 
    def test_gnu_changelog(self):
951
 
        wt = self.make_standard_commit('nicky', authors=[])
952
 
        self.assertFormatterResult('''\
953
 
2005-11-22  Lorem Ipsum  <test@example.com>
954
 
 
955
 
\tadd a
956
 
 
957
 
''',
958
 
            wt.branch, log.GnuChangelogLogFormatter)
959
 
 
960
 
    def test_with_authors(self):
961
 
        wt = self.make_standard_commit('nicky',
962
 
            authors=['Fooa Fooz <foo@example.com>',
963
 
                     'Bari Baro <bar@example.com>'])
964
 
        self.assertFormatterResult('''\
965
 
2005-11-22  Fooa Fooz  <foo@example.com>
966
 
 
967
 
\tadd a
968
 
 
969
 
''',
970
 
            wt.branch, log.GnuChangelogLogFormatter)
971
 
 
972
 
    def test_verbose(self):
973
 
        wt = self.make_standard_commit('nicky')
974
 
        self.assertFormatterResult('''\
975
 
2005-11-22  John Doe  <jdoe@example.com>
976
 
 
977
 
\t* a:
978
 
 
979
 
\tadd a
980
 
 
981
 
''',
982
 
            wt.branch, log.GnuChangelogLogFormatter,
983
 
            show_log_kwargs=dict(verbose=True))
 
880
class TestGetViewRevisions(tests.TestCaseWithTransport, TestLogMixin):
 
881
 
 
882
    def _get_view_revisions(self, *args, **kwargs):
 
883
        return self.applyDeprecated(symbol_versioning.deprecated_in((2, 2, 0)),
 
884
                                    log.get_view_revisions, *args, **kwargs)
 
885
 
 
886
    def make_tree_with_commits(self):
 
887
        """Create a tree with well-known revision ids"""
 
888
        wt = self.make_branch_and_tree('tree1')
 
889
        self.wt_commit(wt, 'commit one', rev_id='1')
 
890
        self.wt_commit(wt, 'commit two', rev_id='2')
 
891
        self.wt_commit(wt, 'commit three', rev_id='3')
 
892
        mainline_revs = [None, '1', '2', '3']
 
893
        rev_nos = {'1': 1, '2': 2, '3': 3}
 
894
        return mainline_revs, rev_nos, wt
 
895
 
 
896
    def make_tree_with_merges(self):
 
897
        """Create a tree with well-known revision ids and a merge"""
 
898
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
899
        tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
 
900
        self.wt_commit(tree2, 'four-a', rev_id='4a')
 
901
        wt.merge_from_branch(tree2.branch)
 
902
        self.wt_commit(wt, 'four-b', rev_id='4b')
 
903
        mainline_revs.append('4b')
 
904
        rev_nos['4b'] = 4
 
905
        # 4a: 3.1.1
 
906
        return mainline_revs, rev_nos, wt
 
907
 
 
908
    def make_branch_with_many_merges(self):
 
909
        """Create a tree with well-known revision ids"""
 
910
        builder = self.make_branch_builder('tree1')
 
911
        builder.start_series()
 
912
        builder.build_snapshot('1', None, [
 
913
            ('add', ('', 'TREE_ROOT', 'directory', '')),
 
914
            ('add', ('f', 'f-id', 'file', '1\n'))])
 
915
        builder.build_snapshot('2', ['1'], [])
 
916
        builder.build_snapshot('3a', ['2'], [
 
917
            ('modify', ('f-id', '1\n2\n3a\n'))])
 
918
        builder.build_snapshot('3b', ['2', '3a'], [
 
919
            ('modify', ('f-id', '1\n2\n3a\n'))])
 
920
        builder.build_snapshot('3c', ['2', '3b'], [
 
921
            ('modify', ('f-id', '1\n2\n3a\n'))])
 
922
        builder.build_snapshot('4a', ['3b'], [])
 
923
        builder.build_snapshot('4b', ['3c', '4a'], [])
 
924
        builder.finish_series()
 
925
 
 
926
        # 1
 
927
        # |
 
928
        # 2-.
 
929
        # |\ \
 
930
        # | | 3a
 
931
        # | |/
 
932
        # | 3b
 
933
        # |/|
 
934
        # 3c4a
 
935
        # |/
 
936
        # 4b
 
937
 
 
938
        mainline_revs = [None, '1', '2', '3c', '4b']
 
939
        rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
 
940
        full_rev_nos_for_reference = {
 
941
            '1': '1',
 
942
            '2': '2',
 
943
            '3a': '2.1.1', #first commit tree 3
 
944
            '3b': '2.2.1', # first commit tree 2
 
945
            '3c': '3', #merges 3b to main
 
946
            '4a': '2.2.2', # second commit tree 2
 
947
            '4b': '4', # merges 4a to main
 
948
            }
 
949
        return mainline_revs, rev_nos, builder.get_branch()
 
950
 
 
951
    def test_get_view_revisions_forward(self):
 
952
        """Test the get_view_revisions method"""
 
953
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
954
        wt.lock_read()
 
955
        self.addCleanup(wt.unlock)
 
956
        revisions = list(self._get_view_revisions(
 
957
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
958
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
 
959
                         revisions)
 
960
        revisions2 = list(self._get_view_revisions(
 
961
                mainline_revs, rev_nos, wt.branch, 'forward',
 
962
                include_merges=False))
 
963
        self.assertEqual(revisions, revisions2)
 
964
 
 
965
    def test_get_view_revisions_reverse(self):
 
966
        """Test the get_view_revisions with reverse"""
 
967
        mainline_revs, rev_nos, wt = self.make_tree_with_commits()
 
968
        wt.lock_read()
 
969
        self.addCleanup(wt.unlock)
 
970
        revisions = list(self._get_view_revisions(
 
971
                mainline_revs, rev_nos, wt.branch, 'reverse'))
 
972
        self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
 
973
                         revisions)
 
974
        revisions2 = list(self._get_view_revisions(
 
975
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
976
                include_merges=False))
 
977
        self.assertEqual(revisions, revisions2)
 
978
 
 
979
    def test_get_view_revisions_merge(self):
 
980
        """Test get_view_revisions when there are merges"""
 
981
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
982
        wt.lock_read()
 
983
        self.addCleanup(wt.unlock)
 
984
        revisions = list(self._get_view_revisions(
 
985
                mainline_revs, rev_nos, wt.branch, 'forward'))
 
986
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
987
                          ('4b', '4', 0), ('4a', '3.1.1', 1)],
 
988
                         revisions)
 
989
        revisions = list(self._get_view_revisions(
 
990
                mainline_revs, rev_nos, wt.branch, 'forward',
 
991
                include_merges=False))
 
992
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
 
993
                          ('4b', '4', 0)],
 
994
                         revisions)
 
995
 
 
996
    def test_get_view_revisions_merge_reverse(self):
 
997
        """Test get_view_revisions in reverse when there are merges"""
 
998
        mainline_revs, rev_nos, wt = self.make_tree_with_merges()
 
999
        wt.lock_read()
 
1000
        self.addCleanup(wt.unlock)
 
1001
        revisions = list(self._get_view_revisions(
 
1002
                mainline_revs, rev_nos, wt.branch, 'reverse'))
 
1003
        self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
 
1004
                          ('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
 
1005
                         revisions)
 
1006
        revisions = list(self._get_view_revisions(
 
1007
                mainline_revs, rev_nos, wt.branch, 'reverse',
 
1008
                include_merges=False))
 
1009
        self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
 
1010
                          ('1', '1', 0)],
 
1011
                         revisions)
 
1012
 
 
1013
    def test_get_view_revisions_merge2(self):
 
1014
        """Test get_view_revisions when there are merges"""
 
1015
        mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
 
1016
        b.lock_read()
 
1017
        self.addCleanup(b.unlock)
 
1018
        revisions = list(self._get_view_revisions(
 
1019
                mainline_revs, rev_nos, b, 'forward'))
 
1020
        expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
1021
                    ('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
 
1022
                    ('4a', '2.2.2', 1)]
 
1023
        self.assertEqual(expected, revisions)
 
1024
        revisions = list(self._get_view_revisions(
 
1025
                mainline_revs, rev_nos, b, 'forward',
 
1026
                include_merges=False))
 
1027
        self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
 
1028
                          ('4b', '4', 0)],
 
1029
                         revisions)
 
1030
 
 
1031
    def test_file_id_for_range(self):
 
1032
        mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
 
1033
        b.lock_read()
 
1034
        self.addCleanup(b.unlock)
 
1035
 
 
1036
        def rev_from_rev_id(revid, branch):
 
1037
            revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
 
1038
            return revspec.in_history(branch)
 
1039
 
 
1040
        def view_revs(start_rev, end_rev, file_id, direction):
 
1041
            revs = self.applyDeprecated(
 
1042
                symbol_versioning.deprecated_in((2, 2, 0)),
 
1043
                log.calculate_view_revisions,
 
1044
                b,
 
1045
                start_rev, # start_revision
 
1046
                end_rev, # end_revision
 
1047
                direction, # direction
 
1048
                file_id, # specific_fileid
 
1049
                True, # generate_merge_revisions
 
1050
                )
 
1051
            return revs
 
1052
 
 
1053
        rev_3a = rev_from_rev_id('3a', b)
 
1054
        rev_4b = rev_from_rev_id('4b', b)
 
1055
        self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1),
 
1056
                          ('3a', '2.1.1', 2)],
 
1057
                          view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
 
1058
        # Note: 3c still appears before 3a here because of depth-based sorting
 
1059
        self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1),
 
1060
                          ('3a', '2.1.1', 2)],
 
1061
                          view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
 
1062
 
 
1063
 
 
1064
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
 
1065
 
 
1066
    def get_view_revisions(self, *args):
 
1067
        return self.applyDeprecated(symbol_versioning.deprecated_in((2, 2, 0)),
 
1068
                                    log.get_view_revisions, *args)
 
1069
 
 
1070
    def create_tree_with_single_merge(self):
 
1071
        """Create a branch with a moderate layout.
 
1072
 
 
1073
        The revision graph looks like:
 
1074
 
 
1075
           A
 
1076
           |\
 
1077
           B C
 
1078
           |/
 
1079
           D
 
1080
 
 
1081
        In this graph, A introduced files f1 and f2 and f3.
 
1082
        B modifies f1 and f3, and C modifies f2 and f3.
 
1083
        D merges the changes from B and C and resolves the conflict for f3.
 
1084
        """
 
1085
        # TODO: jam 20070218 This seems like it could really be done
 
1086
        #       with make_branch_and_memory_tree() if we could just
 
1087
        #       create the content of those files.
 
1088
        # TODO: jam 20070218 Another alternative is that we would really
 
1089
        #       like to only create this tree 1 time for all tests that
 
1090
        #       use it. Since 'log' only uses the tree in a readonly
 
1091
        #       fashion, it seems a shame to regenerate an identical
 
1092
        #       tree for each test.
 
1093
        # TODO: vila 20100122 One way to address the shame above will be to
 
1094
        #       create a memory tree during test parametrization and give a
 
1095
        #       *copy* of this tree to each test. Copying a memory tree ought
 
1096
        #       to be cheap, at least cheaper than creating them with such
 
1097
        #       complex setups.
 
1098
        tree = self.make_branch_and_tree('tree')
 
1099
        tree.lock_write()
 
1100
        self.addCleanup(tree.unlock)
 
1101
 
 
1102
        self.build_tree_contents([('tree/f1', 'A\n'),
 
1103
                                  ('tree/f2', 'A\n'),
 
1104
                                  ('tree/f3', 'A\n'),
 
1105
                                 ])
 
1106
        tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
 
1107
        tree.commit('A', rev_id='A')
 
1108
 
 
1109
        self.build_tree_contents([('tree/f2', 'A\nC\n'),
 
1110
                                  ('tree/f3', 'A\nC\n'),
 
1111
                                 ])
 
1112
        tree.commit('C', rev_id='C')
 
1113
        # Revert back to A to build the other history.
 
1114
        tree.set_last_revision('A')
 
1115
        tree.branch.set_last_revision_info(1, 'A')
 
1116
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
1117
                                  ('tree/f2', 'A\n'),
 
1118
                                  ('tree/f3', 'A\nB\n'),
 
1119
                                 ])
 
1120
        tree.commit('B', rev_id='B')
 
1121
        tree.set_parent_ids(['B', 'C'])
 
1122
        self.build_tree_contents([('tree/f1', 'A\nB\n'),
 
1123
                                  ('tree/f2', 'A\nC\n'),
 
1124
                                  ('tree/f3', 'A\nB\nC\n'),
 
1125
                                 ])
 
1126
        tree.commit('D', rev_id='D')
 
1127
 
 
1128
        # Switch to a read lock for this tree.
 
1129
        # We still have an addCleanup(tree.unlock) pending
 
1130
        tree.unlock()
 
1131
        tree.lock_read()
 
1132
        return tree
 
1133
 
 
1134
    def check_delta(self, delta, **kw):
 
1135
        """Check the filenames touched by a delta are as expected.
 
1136
 
 
1137
        Caller only have to pass in the list of files for each part, all
 
1138
        unspecified parts are considered empty (and checked as such).
 
1139
        """
 
1140
        for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
 
1141
            # By default we expect an empty list
 
1142
            expected = kw.get(n, [])
 
1143
            # strip out only the path components
 
1144
            got = [x[0] for x in getattr(delta, n)]
 
1145
            self.assertEqual(expected, got)
 
1146
 
 
1147
    def test_tree_with_single_merge(self):
 
1148
        """Make sure the tree layout is correct."""
 
1149
        tree = self.create_tree_with_single_merge()
 
1150
        rev_A_tree = tree.branch.repository.revision_tree('A')
 
1151
        rev_B_tree = tree.branch.repository.revision_tree('B')
 
1152
        rev_C_tree = tree.branch.repository.revision_tree('C')
 
1153
        rev_D_tree = tree.branch.repository.revision_tree('D')
 
1154
 
 
1155
        self.check_delta(rev_B_tree.changes_from(rev_A_tree),
 
1156
                         modified=['f1', 'f3'])
 
1157
 
 
1158
        self.check_delta(rev_C_tree.changes_from(rev_A_tree),
 
1159
                         modified=['f2', 'f3'])
 
1160
 
 
1161
        self.check_delta(rev_D_tree.changes_from(rev_B_tree),
 
1162
                         modified=['f2', 'f3'])
 
1163
 
 
1164
        self.check_delta(rev_D_tree.changes_from(rev_C_tree),
 
1165
                         modified=['f1', 'f3'])
 
1166
 
 
1167
    def assertAllRevisionsForFileID(self, tree, file_id, revisions):
 
1168
        """Ensure _filter_revisions_touching_file_id returns the right values.
 
1169
 
 
1170
        Get the return value from _filter_revisions_touching_file_id and make
 
1171
        sure they are correct.
 
1172
        """
 
1173
        # The api for _filter_revisions_touching_file_id is a little crazy.
 
1174
        # So we do the setup here.
 
1175
        mainline = tree.branch.revision_history()
 
1176
        mainline.insert(0, None)
 
1177
        revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
 
1178
        view_revs_iter = self.get_view_revisions(
 
1179
            mainline, revnos, tree.branch, 'reverse', True)
 
1180
        actual_revs = log._filter_revisions_touching_file_id(
 
1181
            tree.branch, file_id, list(view_revs_iter))
 
1182
        self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
 
1183
 
 
1184
    def test_file_id_f1(self):
 
1185
        tree = self.create_tree_with_single_merge()
 
1186
        # f1 should be marked as modified by revisions A and B
 
1187
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
 
1188
 
 
1189
    def test_file_id_f2(self):
 
1190
        tree = self.create_tree_with_single_merge()
 
1191
        # f2 should be marked as modified by revisions A, C, and D
 
1192
        # because D merged the changes from C.
 
1193
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1194
 
 
1195
    def test_file_id_f3(self):
 
1196
        tree = self.create_tree_with_single_merge()
 
1197
        # f3 should be marked as modified by revisions A, B, C, and D
 
1198
        self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
 
1199
 
 
1200
    def test_file_id_with_ghosts(self):
 
1201
        # This is testing bug #209948, where having a ghost would cause
 
1202
        # _filter_revisions_touching_file_id() to fail.
 
1203
        tree = self.create_tree_with_single_merge()
 
1204
        # We need to add a revision, so switch back to a write-locked tree
 
1205
        # (still a single addCleanup(tree.unlock) pending).
 
1206
        tree.unlock()
 
1207
        tree.lock_write()
 
1208
        first_parent = tree.last_revision()
 
1209
        tree.set_parent_ids([first_parent, 'ghost-revision-id'])
 
1210
        self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
 
1211
        tree.commit('commit with a ghost', rev_id='XX')
 
1212
        self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
 
1213
        self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
 
1214
 
 
1215
    def test_unknown_file_id(self):
 
1216
        tree = self.create_tree_with_single_merge()
 
1217
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
 
1218
 
 
1219
    def test_empty_branch_unknown_file_id(self):
 
1220
        tree = self.make_branch_and_tree('tree')
 
1221
        self.assertAllRevisionsForFileID(tree, 'unknown', [])
984
1222
 
985
1223
 
986
1224
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1187
1425
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1188
1426
 
1189
1427
 
1190
 
class TestRevisionNotInBranch(TestCaseForLogFormatter):
1191
 
 
1192
 
    def setup_a_tree(self):
1193
 
        tree = self.make_branch_and_tree('tree')
1194
 
        tree.lock_write()
1195
 
        self.addCleanup(tree.unlock)
1196
 
        kwargs = {
1197
 
            'committer': 'Joe Foo <joe@foo.com>',
1198
 
            'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1199
 
            'timezone': 0, # UTC
1200
 
        }
1201
 
        tree.commit('commit 1a', rev_id='1a', **kwargs)
1202
 
        tree.commit('commit 2a', rev_id='2a', **kwargs)
1203
 
        tree.commit('commit 3a', rev_id='3a', **kwargs)
1204
 
        return tree
1205
 
 
1206
 
    def setup_ab_tree(self):
1207
 
        tree = self.setup_a_tree()
1208
 
        tree.set_last_revision('1a')
1209
 
        tree.branch.set_last_revision_info(1, '1a')
1210
 
        kwargs = {
1211
 
            'committer': 'Joe Foo <joe@foo.com>',
1212
 
            'timestamp': 1132617600, # Mon 2005-11-22 00:00:00 +0000
1213
 
            'timezone': 0, # UTC
1214
 
        }
1215
 
        tree.commit('commit 2b', rev_id='2b', **kwargs)
1216
 
        tree.commit('commit 3b', rev_id='3b', **kwargs)
1217
 
        return tree
1218
 
 
1219
 
    def test_one_revision(self):
1220
 
        tree = self.setup_ab_tree()
1221
 
        lf = LogCatcher()
1222
 
        rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1223
 
        log.show_log(tree.branch, lf, verbose=True, start_revision=rev,
1224
 
                     end_revision=rev)
1225
 
        self.assertEqual(1, len(lf.revisions))
1226
 
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
1227
 
        self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1228
 
 
1229
 
    def test_many_revisions(self):
1230
 
        tree = self.setup_ab_tree()
1231
 
        lf = LogCatcher()
1232
 
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1233
 
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1234
 
        log.show_log(tree.branch, lf, verbose=True, start_revision=start_rev,
1235
 
                     end_revision=end_rev)
1236
 
        self.assertEqual(3, len(lf.revisions))
1237
 
        self.assertEqual(None, lf.revisions[0].revno)   # Out-of-branch
1238
 
        self.assertEqual('3a', lf.revisions[0].rev.revision_id)
1239
 
        self.assertEqual(None, lf.revisions[1].revno)   # Out-of-branch
1240
 
        self.assertEqual('2a', lf.revisions[1].rev.revision_id)
1241
 
        self.assertEqual('1', lf.revisions[2].revno)    # In-branch
1242
 
 
1243
 
    def test_long_format(self):
1244
 
        tree = self.setup_ab_tree()
1245
 
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1246
 
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1247
 
        self.assertFormatterResult("""\
1248
 
------------------------------------------------------------
1249
 
revision-id: 3a
1250
 
committer: Joe Foo <joe@foo.com>
1251
 
branch nick: tree
1252
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1253
 
message:
1254
 
  commit 3a
1255
 
------------------------------------------------------------
1256
 
revision-id: 2a
1257
 
committer: Joe Foo <joe@foo.com>
1258
 
branch nick: tree
1259
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1260
 
message:
1261
 
  commit 2a
1262
 
------------------------------------------------------------
1263
 
revno: 1
1264
 
committer: Joe Foo <joe@foo.com>
1265
 
branch nick: tree
1266
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1267
 
message:
1268
 
  commit 1a
1269
 
""",
1270
 
            tree.branch, log.LongLogFormatter, show_log_kwargs={
1271
 
                'start_revision': start_rev, 'end_revision': end_rev
1272
 
            })
1273
 
 
1274
 
    def test_short_format(self):
1275
 
        tree = self.setup_ab_tree()
1276
 
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1277
 
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1278
 
        self.assertFormatterResult("""\
1279
 
      Joe Foo\t2005-11-22
1280
 
      revision-id:3a
1281
 
      commit 3a
1282
 
 
1283
 
      Joe Foo\t2005-11-22
1284
 
      revision-id:2a
1285
 
      commit 2a
1286
 
 
1287
 
    1 Joe Foo\t2005-11-22
1288
 
      commit 1a
1289
 
 
1290
 
""",
1291
 
            tree.branch, log.ShortLogFormatter, show_log_kwargs={
1292
 
                'start_revision': start_rev, 'end_revision': end_rev
1293
 
            })
1294
 
 
1295
 
    def test_line_format(self):
1296
 
        tree = self.setup_ab_tree()
1297
 
        start_rev = revisionspec.RevisionInfo(tree.branch, None, '1a')
1298
 
        end_rev = revisionspec.RevisionInfo(tree.branch, None, '3a')
1299
 
        self.assertFormatterResult("""\
1300
 
Joe Foo 2005-11-22 commit 3a
1301
 
Joe Foo 2005-11-22 commit 2a
1302
 
1: Joe Foo 2005-11-22 commit 1a
1303
 
""",
1304
 
            tree.branch, log.LineLogFormatter, show_log_kwargs={
1305
 
                'start_revision': start_rev, 'end_revision': end_rev
1306
 
            })
1307
 
 
1308
1428
 
1309
1429
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1310
1430
 
1334
1454
        self.assertFormatterResult("""\
1335
1455
------------------------------------------------------------
1336
1456
revno: 2
1337
 
fixes bugs: test://bug/id test://bug/2
 
1457
fixes bug(s): test://bug/id test://bug/2
1338
1458
author: Joe Bar <joe@bar.com>
1339
1459
committer: Joe Foo <joe@foo.com>
1340
1460
branch nick: work
1345
1465
  message
1346
1466
------------------------------------------------------------
1347
1467
revno: 1
1348
 
fixes bug: test://bug/id
 
1468
fixes bug(s): test://bug/id
1349
1469
committer: Joe Foo <joe@foo.com>
1350
1470
branch nick: work
1351
1471
timestamp: Tue 2005-11-22 00:00:00 +0000
1358
1478
        tree = self.make_commits_with_bugs()
1359
1479
        self.assertFormatterResult("""\
1360
1480
    2 Joe Bar\t2005-11-22
1361
 
      fixes bugs: test://bug/id test://bug/2
 
1481
      fixes bug(s): test://bug/id test://bug/2
1362
1482
      multiline
1363
1483
      log
1364
1484
      message
1365
1485
 
1366
1486
    1 Joe Foo\t2005-11-22
1367
 
      fixes bug: test://bug/id
 
1487
      fixes bug(s): test://bug/id
1368
1488
      simple log message
1369
1489
 
1370
1490
""",
1384
1504
 
1385
1505
    def test_bugs_handler_present(self):
1386
1506
        self.properties_handler_registry.get('bugs_properties_handler')
1387
 
 
1388
 
 
1389
 
class TestLogForAuthors(TestCaseForLogFormatter):
1390
 
 
1391
 
    def setUp(self):
1392
 
        TestCaseForLogFormatter.setUp(self)
1393
 
        self.wt = self.make_standard_commit('nicky',
1394
 
            authors=['John Doe <jdoe@example.com>',
1395
 
                     'Jane Rey <jrey@example.com>'])
1396
 
 
1397
 
    def assertFormatterResult(self, formatter, who, result):
1398
 
        formatter_kwargs = dict()
1399
 
        if who is not None:
1400
 
            author_list_handler = log.author_list_registry.get(who)
1401
 
            formatter_kwargs['author_list_handler'] = author_list_handler
1402
 
        TestCaseForLogFormatter.assertFormatterResult(self, result,
1403
 
            self.wt.branch, formatter, formatter_kwargs=formatter_kwargs)
1404
 
 
1405
 
    def test_line_default(self):
1406
 
        self.assertFormatterResult(log.LineLogFormatter, None, """\
1407
 
1: John Doe 2005-11-22 add a
1408
 
""")
1409
 
 
1410
 
    def test_line_committer(self):
1411
 
        self.assertFormatterResult(log.LineLogFormatter, 'committer', """\
1412
 
1: Lorem Ipsum 2005-11-22 add a
1413
 
""")
1414
 
 
1415
 
    def test_line_first(self):
1416
 
        self.assertFormatterResult(log.LineLogFormatter, 'first', """\
1417
 
1: John Doe 2005-11-22 add a
1418
 
""")
1419
 
 
1420
 
    def test_line_all(self):
1421
 
        self.assertFormatterResult(log.LineLogFormatter, 'all', """\
1422
 
1: John Doe, Jane Rey 2005-11-22 add a
1423
 
""")
1424
 
 
1425
 
 
1426
 
    def test_short_default(self):
1427
 
        self.assertFormatterResult(log.ShortLogFormatter, None, """\
1428
 
    1 John Doe\t2005-11-22
1429
 
      add a
1430
 
 
1431
 
""")
1432
 
 
1433
 
    def test_short_committer(self):
1434
 
        self.assertFormatterResult(log.ShortLogFormatter, 'committer', """\
1435
 
    1 Lorem Ipsum\t2005-11-22
1436
 
      add a
1437
 
 
1438
 
""")
1439
 
 
1440
 
    def test_short_first(self):
1441
 
        self.assertFormatterResult(log.ShortLogFormatter, 'first', """\
1442
 
    1 John Doe\t2005-11-22
1443
 
      add a
1444
 
 
1445
 
""")
1446
 
 
1447
 
    def test_short_all(self):
1448
 
        self.assertFormatterResult(log.ShortLogFormatter, 'all', """\
1449
 
    1 John Doe, Jane Rey\t2005-11-22
1450
 
      add a
1451
 
 
1452
 
""")
1453
 
 
1454
 
    def test_long_default(self):
1455
 
        self.assertFormatterResult(log.LongLogFormatter, None, """\
1456
 
------------------------------------------------------------
1457
 
revno: 1
1458
 
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1459
 
committer: Lorem Ipsum <test@example.com>
1460
 
branch nick: nicky
1461
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1462
 
message:
1463
 
  add a
1464
 
""")
1465
 
 
1466
 
    def test_long_committer(self):
1467
 
        self.assertFormatterResult(log.LongLogFormatter, 'committer', """\
1468
 
------------------------------------------------------------
1469
 
revno: 1
1470
 
committer: Lorem Ipsum <test@example.com>
1471
 
branch nick: nicky
1472
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1473
 
message:
1474
 
  add a
1475
 
""")
1476
 
 
1477
 
    def test_long_first(self):
1478
 
        self.assertFormatterResult(log.LongLogFormatter, 'first', """\
1479
 
------------------------------------------------------------
1480
 
revno: 1
1481
 
author: John Doe <jdoe@example.com>
1482
 
committer: Lorem Ipsum <test@example.com>
1483
 
branch nick: nicky
1484
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1485
 
message:
1486
 
  add a
1487
 
""")
1488
 
 
1489
 
    def test_long_all(self):
1490
 
        self.assertFormatterResult(log.LongLogFormatter, 'all', """\
1491
 
------------------------------------------------------------
1492
 
revno: 1
1493
 
author: John Doe <jdoe@example.com>, Jane Rey <jrey@example.com>
1494
 
committer: Lorem Ipsum <test@example.com>
1495
 
branch nick: nicky
1496
 
timestamp: Tue 2005-11-22 00:00:00 +0000
1497
 
message:
1498
 
  add a
1499
 
""")
1500
 
 
1501
 
    def test_gnu_changelog_default(self):
1502
 
        self.assertFormatterResult(log.GnuChangelogLogFormatter, None, """\
1503
 
2005-11-22  John Doe  <jdoe@example.com>
1504
 
 
1505
 
\tadd a
1506
 
 
1507
 
""")
1508
 
 
1509
 
    def test_gnu_changelog_committer(self):
1510
 
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'committer', """\
1511
 
2005-11-22  Lorem Ipsum  <test@example.com>
1512
 
 
1513
 
\tadd a
1514
 
 
1515
 
""")
1516
 
 
1517
 
    def test_gnu_changelog_first(self):
1518
 
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'first', """\
1519
 
2005-11-22  John Doe  <jdoe@example.com>
1520
 
 
1521
 
\tadd a
1522
 
 
1523
 
""")
1524
 
 
1525
 
    def test_gnu_changelog_all(self):
1526
 
        self.assertFormatterResult(log.GnuChangelogLogFormatter, 'all', """\
1527
 
2005-11-22  John Doe  <jdoe@example.com>, Jane Rey  <jrey@example.com>
1528
 
 
1529
 
\tadd a
1530
 
 
1531
 
""")
1532
 
 
1533
 
 
1534
 
class TestLogExcludeAncestry(tests.TestCaseWithTransport):
1535
 
 
1536
 
    def make_branch_with_alternate_ancestries(self, relpath='.'):
1537
 
        # See test_merge_sorted_exclude_ancestry below for the difference with
1538
 
        # bt.per_branch.test_iter_merge_sorted_revision.
1539
 
        # TestIterMergeSortedRevisionsBushyGraph.
1540
 
        # make_branch_with_alternate_ancestries
1541
 
        # and test_merge_sorted_exclude_ancestry
1542
 
        # See the FIXME in assertLogRevnos too.
1543
 
        builder = branchbuilder.BranchBuilder(self.get_transport(relpath))
1544
 
        # 1
1545
 
        # |\
1546
 
        # 2 \
1547
 
        # |  |
1548
 
        # |  1.1.1
1549
 
        # |  | \
1550
 
        # |  |  1.2.1
1551
 
        # |  | /
1552
 
        # |  1.1.2
1553
 
        # | /
1554
 
        # 3
1555
 
        builder.start_series()
1556
 
        builder.build_snapshot('1', None, [
1557
 
            ('add', ('', 'TREE_ROOT', 'directory', '')),])
1558
 
        builder.build_snapshot('1.1.1', ['1'], [])
1559
 
        builder.build_snapshot('2', ['1'], [])
1560
 
        builder.build_snapshot('1.2.1', ['1.1.1'], [])
1561
 
        builder.build_snapshot('1.1.2', ['1.1.1', '1.2.1'], [])
1562
 
        builder.build_snapshot('3', ['2', '1.1.2'], [])
1563
 
        builder.finish_series()
1564
 
        br = builder.get_branch()
1565
 
        br.lock_read()
1566
 
        self.addCleanup(br.unlock)
1567
 
        return br
1568
 
 
1569
 
    def assertLogRevnos(self, expected_revnos, b, start, end,
1570
 
                        exclude_common_ancestry, generate_merge_revisions=True):
1571
 
        # FIXME: the layering in log makes it hard to test intermediate levels,
1572
 
        # I wish adding filters with their parameters was easier...
1573
 
        # -- vila 20100413
1574
 
        iter_revs = log._calc_view_revisions(
1575
 
            b, start, end, direction='reverse',
1576
 
            generate_merge_revisions=generate_merge_revisions,
1577
 
            exclude_common_ancestry=exclude_common_ancestry)
1578
 
        self.assertEqual(expected_revnos,
1579
 
                         [revid for revid, revno, depth in iter_revs])
1580
 
 
1581
 
    def test_merge_sorted_exclude_ancestry(self):
1582
 
        b = self.make_branch_with_alternate_ancestries()
1583
 
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
1584
 
                             b, '1', '3', exclude_common_ancestry=False)
1585
 
        # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
1586
 
        # it should be mentioned even if merge_sort order will make it appear
1587
 
        # after 1.1.1
1588
 
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
1589
 
                             b, '1.1.1', '3', exclude_common_ancestry=True)
1590
 
 
1591
 
    def test_merge_sorted_simple_revnos_exclude_ancestry(self):
1592
 
        b = self.make_branch_with_alternate_ancestries()
1593
 
        self.assertLogRevnos(['3', '2'],
1594
 
                             b, '1', '3', exclude_common_ancestry=True,
1595
 
                             generate_merge_revisions=False)
1596
 
        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
1597
 
                             b, '1', '3', exclude_common_ancestry=True,
1598
 
                             generate_merge_revisions=True)
1599
 
 
1600
 
 
1601
 
class TestLogDefaults(TestCaseForLogFormatter):
1602
 
    def test_default_log_level(self):
1603
 
        """
1604
 
        Test to ensure that specifying 'levels=1' to make_log_request_dict
1605
 
        doesn't get overwritten when using a LogFormatter that supports more
1606
 
        detail.
1607
 
        Fixes bug #747958.
1608
 
        """
1609
 
        wt = self._prepare_tree_with_merges()
1610
 
        b = wt.branch
1611
 
 
1612
 
        class CustomLogFormatter(log.LogFormatter):
1613
 
            def __init__(self, *args, **kwargs):
1614
 
                super(CustomLogFormatter, self).__init__(*args, **kwargs)
1615
 
                self.revisions = []
1616
 
            def get_levels(self):
1617
 
                # log formatter supports all levels:
1618
 
                return 0
1619
 
            def log_revision(self, revision):
1620
 
                self.revisions.append(revision)
1621
 
 
1622
 
        log_formatter = LogCatcher()
1623
 
        # First request we don't specify number of levels, we should get a
1624
 
        # sensible default (whatever the LogFormatter handles - which in this
1625
 
        # case is 0/everything):
1626
 
        request = log.make_log_request_dict(limit=10)
1627
 
        log.Logger(b, request).show(log_formatter)
1628
 
        # should have all three revisions:
1629
 
        self.assertEquals(len(log_formatter.revisions), 3)
1630
 
 
1631
 
        del log_formatter
1632
 
        log_formatter = LogCatcher()
1633
 
        # now explicitly request mainline revisions only:
1634
 
        request = log.make_log_request_dict(limit=10, levels=1)
1635
 
        log.Logger(b, request).show(log_formatter)
1636
 
        # should now only have 2 revisions:
1637
 
        self.assertEquals(len(log_formatter.revisions), 2)
1638