983
1016
wt.branch, log.GnuChangelogLogFormatter,
984
1017
show_log_kwargs=dict(verbose=True))
986
class TestGetViewRevisions(tests.TestCaseWithTransport, TestLogMixin):
988
def _get_view_revisions(self, *args, **kwargs):
989
return self.applyDeprecated(symbol_versioning.deprecated_in((2, 2, 0)),
990
log.get_view_revisions, *args, **kwargs)
992
def make_tree_with_commits(self):
993
"""Create a tree with well-known revision ids"""
994
wt = self.make_branch_and_tree('tree1')
995
self.wt_commit(wt, 'commit one', rev_id='1')
996
self.wt_commit(wt, 'commit two', rev_id='2')
997
self.wt_commit(wt, 'commit three', rev_id='3')
998
mainline_revs = [None, '1', '2', '3']
999
rev_nos = {'1': 1, '2': 2, '3': 3}
1000
return mainline_revs, rev_nos, wt
1002
def make_tree_with_merges(self):
1003
"""Create a tree with well-known revision ids and a merge"""
1004
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1005
tree2 = wt.bzrdir.sprout('tree2').open_workingtree()
1006
self.wt_commit(tree2, 'four-a', rev_id='4a')
1007
wt.merge_from_branch(tree2.branch)
1008
self.wt_commit(wt, 'four-b', rev_id='4b')
1009
mainline_revs.append('4b')
1012
return mainline_revs, rev_nos, wt
1014
def make_branch_with_many_merges(self):
1015
"""Create a tree with well-known revision ids"""
1016
builder = self.make_branch_builder('tree1')
1017
builder.start_series()
1018
builder.build_snapshot('1', None, [
1019
('add', ('', 'TREE_ROOT', 'directory', '')),
1020
('add', ('f', 'f-id', 'file', '1\n'))])
1021
builder.build_snapshot('2', ['1'], [])
1022
builder.build_snapshot('3a', ['2'], [
1023
('modify', ('f-id', '1\n2\n3a\n'))])
1024
builder.build_snapshot('3b', ['2', '3a'], [
1025
('modify', ('f-id', '1\n2\n3a\n'))])
1026
builder.build_snapshot('3c', ['2', '3b'], [
1027
('modify', ('f-id', '1\n2\n3a\n'))])
1028
builder.build_snapshot('4a', ['3b'], [])
1029
builder.build_snapshot('4b', ['3c', '4a'], [])
1030
builder.finish_series()
1044
mainline_revs = [None, '1', '2', '3c', '4b']
1045
rev_nos = {'1':1, '2':2, '3c': 3, '4b':4}
1046
full_rev_nos_for_reference = {
1049
'3a': '2.1.1', #first commit tree 3
1050
'3b': '2.2.1', # first commit tree 2
1051
'3c': '3', #merges 3b to main
1052
'4a': '2.2.2', # second commit tree 2
1053
'4b': '4', # merges 4a to main
1055
return mainline_revs, rev_nos, builder.get_branch()
1057
def test_get_view_revisions_forward(self):
1058
"""Test the get_view_revisions method"""
1059
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1061
self.addCleanup(wt.unlock)
1062
revisions = list(self._get_view_revisions(
1063
mainline_revs, rev_nos, wt.branch, 'forward'))
1064
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0)],
1066
revisions2 = list(self._get_view_revisions(
1067
mainline_revs, rev_nos, wt.branch, 'forward',
1068
include_merges=False))
1069
self.assertEqual(revisions, revisions2)
1071
def test_get_view_revisions_reverse(self):
1072
"""Test the get_view_revisions with reverse"""
1073
mainline_revs, rev_nos, wt = self.make_tree_with_commits()
1075
self.addCleanup(wt.unlock)
1076
revisions = list(self._get_view_revisions(
1077
mainline_revs, rev_nos, wt.branch, 'reverse'))
1078
self.assertEqual([('3', '3', 0), ('2', '2', 0), ('1', '1', 0), ],
1080
revisions2 = list(self._get_view_revisions(
1081
mainline_revs, rev_nos, wt.branch, 'reverse',
1082
include_merges=False))
1083
self.assertEqual(revisions, revisions2)
1085
def test_get_view_revisions_merge(self):
1086
"""Test get_view_revisions when there are merges"""
1087
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1089
self.addCleanup(wt.unlock)
1090
revisions = list(self._get_view_revisions(
1091
mainline_revs, rev_nos, wt.branch, 'forward'))
1092
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1093
('4b', '4', 0), ('4a', '3.1.1', 1)],
1095
revisions = list(self._get_view_revisions(
1096
mainline_revs, rev_nos, wt.branch, 'forward',
1097
include_merges=False))
1098
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3', '3', 0),
1102
def test_get_view_revisions_merge_reverse(self):
1103
"""Test get_view_revisions in reverse when there are merges"""
1104
mainline_revs, rev_nos, wt = self.make_tree_with_merges()
1106
self.addCleanup(wt.unlock)
1107
revisions = list(self._get_view_revisions(
1108
mainline_revs, rev_nos, wt.branch, 'reverse'))
1109
self.assertEqual([('4b', '4', 0), ('4a', '3.1.1', 1),
1110
('3', '3', 0), ('2', '2', 0), ('1', '1', 0)],
1112
revisions = list(self._get_view_revisions(
1113
mainline_revs, rev_nos, wt.branch, 'reverse',
1114
include_merges=False))
1115
self.assertEqual([('4b', '4', 0), ('3', '3', 0), ('2', '2', 0),
1119
def test_get_view_revisions_merge2(self):
1120
"""Test get_view_revisions when there are merges"""
1121
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1123
self.addCleanup(b.unlock)
1124
revisions = list(self._get_view_revisions(
1125
mainline_revs, rev_nos, b, 'forward'))
1126
expected = [('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1127
('3b', '2.2.1', 1), ('3a', '2.1.1', 2), ('4b', '4', 0),
1129
self.assertEqual(expected, revisions)
1130
revisions = list(self._get_view_revisions(
1131
mainline_revs, rev_nos, b, 'forward',
1132
include_merges=False))
1133
self.assertEqual([('1', '1', 0), ('2', '2', 0), ('3c', '3', 0),
1137
def test_file_id_for_range(self):
1138
mainline_revs, rev_nos, b = self.make_branch_with_many_merges()
1140
self.addCleanup(b.unlock)
1142
def rev_from_rev_id(revid, branch):
1143
revspec = revisionspec.RevisionSpec.from_string('revid:%s' % revid)
1144
return revspec.in_history(branch)
1146
def view_revs(start_rev, end_rev, file_id, direction):
1147
revs = self.applyDeprecated(
1148
symbol_versioning.deprecated_in((2, 2, 0)),
1149
log.calculate_view_revisions,
1151
start_rev, # start_revision
1152
end_rev, # end_revision
1153
direction, # direction
1154
file_id, # specific_fileid
1155
True, # generate_merge_revisions
1159
rev_3a = rev_from_rev_id('3a', b)
1160
rev_4b = rev_from_rev_id('4b', b)
1161
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1),
1162
('3a', '2.1.1', 2)],
1163
view_revs(rev_3a, rev_4b, 'f-id', 'reverse'))
1164
# Note: 3c still appears before 3a here because of depth-based sorting
1165
self.assertEqual([('3c', '3', 0), ('3b', '2.2.1', 1),
1166
('3a', '2.1.1', 2)],
1167
view_revs(rev_3a, rev_4b, 'f-id', 'forward'))
1170
class TestGetRevisionsTouchingFileID(tests.TestCaseWithTransport):
1172
def get_view_revisions(self, *args):
1173
return self.applyDeprecated(symbol_versioning.deprecated_in((2, 2, 0)),
1174
log.get_view_revisions, *args)
1176
def create_tree_with_single_merge(self):
1177
"""Create a branch with a moderate layout.
1179
The revision graph looks like:
1187
In this graph, A introduced files f1 and f2 and f3.
1188
B modifies f1 and f3, and C modifies f2 and f3.
1189
D merges the changes from B and C and resolves the conflict for f3.
1191
# TODO: jam 20070218 This seems like it could really be done
1192
# with make_branch_and_memory_tree() if we could just
1193
# create the content of those files.
1194
# TODO: jam 20070218 Another alternative is that we would really
1195
# like to only create this tree 1 time for all tests that
1196
# use it. Since 'log' only uses the tree in a readonly
1197
# fashion, it seems a shame to regenerate an identical
1198
# tree for each test.
1199
# TODO: vila 20100122 One way to address the shame above will be to
1200
# create a memory tree during test parametrization and give a
1201
# *copy* of this tree to each test. Copying a memory tree ought
1202
# to be cheap, at least cheaper than creating them with such
1204
tree = self.make_branch_and_tree('tree')
1206
self.addCleanup(tree.unlock)
1208
self.build_tree_contents([('tree/f1', 'A\n'),
1212
tree.add(['f1', 'f2', 'f3'], ['f1-id', 'f2-id', 'f3-id'])
1213
tree.commit('A', rev_id='A')
1215
self.build_tree_contents([('tree/f2', 'A\nC\n'),
1216
('tree/f3', 'A\nC\n'),
1218
tree.commit('C', rev_id='C')
1219
# Revert back to A to build the other history.
1220
tree.set_last_revision('A')
1221
tree.branch.set_last_revision_info(1, 'A')
1222
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1224
('tree/f3', 'A\nB\n'),
1226
tree.commit('B', rev_id='B')
1227
tree.set_parent_ids(['B', 'C'])
1228
self.build_tree_contents([('tree/f1', 'A\nB\n'),
1229
('tree/f2', 'A\nC\n'),
1230
('tree/f3', 'A\nB\nC\n'),
1232
tree.commit('D', rev_id='D')
1234
# Switch to a read lock for this tree.
1235
# We still have an addCleanup(tree.unlock) pending
1240
def check_delta(self, delta, **kw):
1241
"""Check the filenames touched by a delta are as expected.
1243
Caller only have to pass in the list of files for each part, all
1244
unspecified parts are considered empty (and checked as such).
1246
for n in 'added', 'removed', 'renamed', 'modified', 'unchanged':
1247
# By default we expect an empty list
1248
expected = kw.get(n, [])
1249
# strip out only the path components
1250
got = [x[0] for x in getattr(delta, n)]
1251
self.assertEqual(expected, got)
1253
def test_tree_with_single_merge(self):
1254
"""Make sure the tree layout is correct."""
1255
tree = self.create_tree_with_single_merge()
1256
rev_A_tree = tree.branch.repository.revision_tree('A')
1257
rev_B_tree = tree.branch.repository.revision_tree('B')
1258
rev_C_tree = tree.branch.repository.revision_tree('C')
1259
rev_D_tree = tree.branch.repository.revision_tree('D')
1261
self.check_delta(rev_B_tree.changes_from(rev_A_tree),
1262
modified=['f1', 'f3'])
1264
self.check_delta(rev_C_tree.changes_from(rev_A_tree),
1265
modified=['f2', 'f3'])
1267
self.check_delta(rev_D_tree.changes_from(rev_B_tree),
1268
modified=['f2', 'f3'])
1270
self.check_delta(rev_D_tree.changes_from(rev_C_tree),
1271
modified=['f1', 'f3'])
1273
def assertAllRevisionsForFileID(self, tree, file_id, revisions):
1274
"""Ensure _filter_revisions_touching_file_id returns the right values.
1276
Get the return value from _filter_revisions_touching_file_id and make
1277
sure they are correct.
1279
# The api for _filter_revisions_touching_file_id is a little crazy.
1280
# So we do the setup here.
1281
mainline = tree.branch.revision_history()
1282
mainline.insert(0, None)
1283
revnos = dict((rev, idx+1) for idx, rev in enumerate(mainline))
1284
view_revs_iter = self.get_view_revisions(
1285
mainline, revnos, tree.branch, 'reverse', True)
1286
actual_revs = log._filter_revisions_touching_file_id(
1287
tree.branch, file_id, list(view_revs_iter))
1288
self.assertEqual(revisions, [r for r, revno, depth in actual_revs])
1290
def test_file_id_f1(self):
1291
tree = self.create_tree_with_single_merge()
1292
# f1 should be marked as modified by revisions A and B
1293
self.assertAllRevisionsForFileID(tree, 'f1-id', ['B', 'A'])
1295
def test_file_id_f2(self):
1296
tree = self.create_tree_with_single_merge()
1297
# f2 should be marked as modified by revisions A, C, and D
1298
# because D merged the changes from C.
1299
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1301
def test_file_id_f3(self):
1302
tree = self.create_tree_with_single_merge()
1303
# f3 should be marked as modified by revisions A, B, C, and D
1304
self.assertAllRevisionsForFileID(tree, 'f3-id', ['D', 'C', 'B', 'A'])
1306
def test_file_id_with_ghosts(self):
1307
# This is testing bug #209948, where having a ghost would cause
1308
# _filter_revisions_touching_file_id() to fail.
1309
tree = self.create_tree_with_single_merge()
1310
# We need to add a revision, so switch back to a write-locked tree
1311
# (still a single addCleanup(tree.unlock) pending).
1314
first_parent = tree.last_revision()
1315
tree.set_parent_ids([first_parent, 'ghost-revision-id'])
1316
self.build_tree_contents([('tree/f1', 'A\nB\nXX\n')])
1317
tree.commit('commit with a ghost', rev_id='XX')
1318
self.assertAllRevisionsForFileID(tree, 'f1-id', ['XX', 'B', 'A'])
1319
self.assertAllRevisionsForFileID(tree, 'f2-id', ['D', 'C', 'A'])
1321
def test_unknown_file_id(self):
1322
tree = self.create_tree_with_single_merge()
1323
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1325
def test_empty_branch_unknown_file_id(self):
1326
tree = self.make_branch_and_tree('tree')
1327
self.assertAllRevisionsForFileID(tree, 'unknown', [])
1330
1020
class TestShowChangedRevisions(tests.TestCaseWithTransport):