~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_log.py

  • Committer: Jelmer Vernooij
  • Date: 2011-12-14 12:15:44 UTC
  • mto: This revision was merged to the branch mainline in revision 6365.
  • Revision ID: jelmer@samba.org-20111214121544-v07cbvmi30re6q7w
s/NoVfsCalls/ContainsNoVfsCalls/.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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,
21
22
    errors,
22
23
    log,
23
24
    registry,
24
25
    revision,
25
26
    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
 
119
120
class LogCatcher(log.LogFormatter):
120
121
    """Pull log messages into a list rather than displaying them.
121
122
 
321
322
    1 Joe Foo\t2005-11-22
322
323
      rev-1
323
324
 
324
 
Use --include-merges or -n0 to see merged revisions.
 
325
Use --include-merged or -n0 to see merged revisions.
325
326
""",
326
327
            wt.branch, log.ShortLogFormatter,
327
328
            formatter_kwargs=dict(show_advice=True))
370
371
            wt.branch, log.ShortLogFormatter,
371
372
            show_log_kwargs=dict(start_revision=rev, end_revision=rev))
372
373
 
 
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
 
373
400
 
374
401
class TestShortLogFormatterWithMergeRevisions(TestCaseForLogFormatter):
375
402
 
658
685
        self.assertEqualDiff('''custom_prop_name: test_value\n''',
659
686
                             sio.getvalue())
660
687
 
 
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
 
661
729
 
662
730
class TestLongLogFormatterWithoutMergeRevisions(TestCaseForLogFormatter):
663
731
 
877
945
            formatter_kwargs=dict(levels=0))
878
946
 
879
947
 
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', [])
 
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))
1222
984
 
1223
985
 
1224
986
class TestShowChangedRevisions(tests.TestCaseWithTransport):
1425
1187
        self.assertNotContainsRe(s.getvalue(), 'Added Revisions:')
1426
1188
 
1427
1189
 
 
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
 
1428
1308
 
1429
1309
class TestLogWithBugs(TestCaseForLogFormatter, TestLogMixin):
1430
1310
 
1454
1334
        self.assertFormatterResult("""\
1455
1335
------------------------------------------------------------
1456
1336
revno: 2
1457
 
fixes bug(s): test://bug/id test://bug/2
 
1337
fixes bugs: test://bug/id test://bug/2
1458
1338
author: Joe Bar <joe@bar.com>
1459
1339
committer: Joe Foo <joe@foo.com>
1460
1340
branch nick: work
1465
1345
  message
1466
1346
------------------------------------------------------------
1467
1347
revno: 1
1468
 
fixes bug(s): test://bug/id
 
1348
fixes bug: test://bug/id
1469
1349
committer: Joe Foo <joe@foo.com>
1470
1350
branch nick: work
1471
1351
timestamp: Tue 2005-11-22 00:00:00 +0000
1478
1358
        tree = self.make_commits_with_bugs()
1479
1359
        self.assertFormatterResult("""\
1480
1360
    2 Joe Bar\t2005-11-22
1481
 
      fixes bug(s): test://bug/id test://bug/2
 
1361
      fixes bugs: test://bug/id test://bug/2
1482
1362
      multiline
1483
1363
      log
1484
1364
      message
1485
1365
 
1486
1366
    1 Joe Foo\t2005-11-22
1487
 
      fixes bug(s): test://bug/id
 
1367
      fixes bug: test://bug/id
1488
1368
      simple log message
1489
1369
 
1490
1370
""",
1504
1384
 
1505
1385
    def test_bugs_handler_present(self):
1506
1386
        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 were 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