1# Copyright (C) 2009, 2010 Canonical Ltd 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""Tests for Branch.iter_merge_sorted_revisions()""" 18 19from breezy import ( 20 errors, 21 revision, 22 tests, 23 ) 24 25from breezy.tests import per_branch 26 27 28class TestIterMergeSortedRevisionsSimpleGraph(per_branch.TestCaseWithBranch): 29 30 def setUp(self): 31 super(TestIterMergeSortedRevisionsSimpleGraph, self).setUp() 32 self.revids = {} 33 builder = self.make_builder_with_merges('.') 34 self.branch = builder.get_branch() 35 self.branch.lock_read() 36 self.addCleanup(self.branch.unlock) 37 38 def make_snapshot(self, builder, parents, revid_name): 39 self.assertNotIn(revid_name, self.revids) 40 if parents is None: 41 files = [('add', ('', None, 'directory', '')), ] 42 else: 43 parents = [self.revids[name] for name in parents] 44 files = [] 45 self.revids[revid_name] = builder.build_snapshot( 46 parents, files, message="Revision %s" % revid_name) 47 48 def make_builder_with_merges(self, relpath): 49 try: 50 builder = self.make_branch_builder(relpath) 51 except (errors.TransportNotPossible, errors.UninitializableFormat): 52 raise tests.TestNotApplicable('format not directly constructable') 53 builder.start_series() 54 # 1 55 # |\ 56 # 2 | 57 # | | 58 # | 1.1.1 59 # |/ 60 # 3 61 self.make_snapshot(builder, None, '1') 62 self.make_snapshot(builder, ['1'], '1.1.1') 63 self.make_snapshot(builder, ['1'], '2') 64 self.make_snapshot(builder, ['2', '1.1.1'], '3') 65 builder.finish_series() 66 return builder 67 68 def assertIterRevids(self, expected, *args, **kwargs): 69 if kwargs.get('stop_revision_id') is not None: 70 kwargs['stop_revision_id'] = self.revids[kwargs['stop_revision_id']] 71 if kwargs.get('start_revision_id') is not None: 72 kwargs['start_revision_id'] = self.revids[kwargs['start_revision_id']] 73 # We don't care about depths and revnos here, only about returning the 74 # right revids. 75 revids = [revid for (revid, depth, revno, eom) in 76 self.branch.iter_merge_sorted_revisions(*args, **kwargs)] 77 self.assertEqual([self.revids[short] for short in expected], revids) 78 79 def test_merge_sorted(self): 80 self.assertIterRevids(['3', '1.1.1', '2', '1']) 81 82 def test_merge_sorted_range(self): 83 self.assertIterRevids(['1.1.1'], 84 start_revision_id='1.1.1', stop_revision_id='1') 85 86 def test_merge_sorted_range_start_only(self): 87 self.assertIterRevids(['1.1.1', '1'], 88 start_revision_id='1.1.1') 89 90 def test_merge_sorted_range_stop_exclude(self): 91 self.assertIterRevids(['3', '1.1.1', '2'], stop_revision_id='1') 92 93 def test_merge_sorted_range_stop_include(self): 94 self.assertIterRevids(['3', '1.1.1', '2'], 95 stop_revision_id='2', stop_rule='include') 96 97 def test_merge_sorted_range_stop_with_merges(self): 98 self.assertIterRevids(['3', '1.1.1'], 99 stop_revision_id='3', stop_rule='with-merges') 100 101 def test_merge_sorted_range_stop_with_merges_can_show_non_parents(self): 102 # 1.1.1 gets logged before the end revision is reached. 103 # so it is returned even though 1.1.1 is not a parent of 2. 104 self.assertIterRevids(['3', '1.1.1', '2'], 105 stop_revision_id='2', stop_rule='with-merges') 106 107 def test_merge_sorted_range_stop_with_merges_ignore_non_parents(self): 108 # 2 is not a parent of 1.1.1 so it must not be returned 109 self.assertIterRevids(['3', '1.1.1'], 110 stop_revision_id='1.1.1', stop_rule='with-merges') 111 112 def test_merge_sorted_single_stop_exclude(self): 113 # from X..X exclusive is an empty result 114 self.assertIterRevids([], start_revision_id='3', stop_revision_id='3') 115 116 def test_merge_sorted_single_stop_include(self): 117 # from X..X inclusive is [X] 118 self.assertIterRevids(['3'], 119 start_revision_id='3', stop_revision_id='3', 120 stop_rule='include') 121 122 def test_merge_sorted_single_stop_with_merges(self): 123 self.assertIterRevids(['3', '1.1.1'], 124 start_revision_id='3', stop_revision_id='3', 125 stop_rule='with-merges') 126 127 def test_merge_sorted_forward(self): 128 self.assertIterRevids(['1', '2', '1.1.1', '3'], direction='forward') 129 130 def test_merge_sorted_range_forward(self): 131 self.assertIterRevids(['1.1.1'], 132 start_revision_id='1.1.1', stop_revision_id='1', 133 direction='forward') 134 135 def test_merge_sorted_range_start_only_forward(self): 136 self.assertIterRevids(['1', '1.1.1'], 137 start_revision_id='1.1.1', direction='forward') 138 139 def test_merge_sorted_range_stop_exclude_forward(self): 140 self.assertIterRevids(['2', '1.1.1', '3'], 141 stop_revision_id='1', direction='forward') 142 143 def test_merge_sorted_range_stop_include_forward(self): 144 self.assertIterRevids(['2', '1.1.1', '3'], 145 stop_revision_id='2', stop_rule='include', 146 direction='forward') 147 148 def test_merge_sorted_range_stop_with_merges_forward(self): 149 self.assertIterRevids(['1.1.1', '3'], 150 stop_revision_id='3', stop_rule='with-merges', 151 direction='forward') 152 153 154class TestIterMergeSortedRevisionsBushyGraph(per_branch.TestCaseWithBranch): 155 156 def setUp(self): 157 super(TestIterMergeSortedRevisionsBushyGraph, self).setUp() 158 self.revids = {} 159 160 def make_branch_builder(self, relpath): 161 try: 162 builder = super(TestIterMergeSortedRevisionsBushyGraph, 163 self).make_branch_builder(relpath) 164 except (errors.TransportNotPossible, errors.UninitializableFormat): 165 raise tests.TestNotApplicable('format not directly constructable') 166 return builder 167 168 def make_snapshot(self, builder, parents, revid_name): 169 self.assertNotIn(revid_name, self.revids) 170 if parents is None: 171 files = [('add', ('', None, 'directory', '')), ] 172 else: 173 parents = [self.revids[name] for name in parents] 174 files = [] 175 self.revids[revid_name] = builder.build_snapshot( 176 parents, files, message="Revision %s" % revid_name) 177 178 def make_branch_with_embedded_merges(self, relpath='.'): 179 builder = self.make_branch_builder(relpath) 180 181 # 1 182 # |\ 183 # | 1.1.1 184 # | / 185 # 2 186 # | \ 187 # 3 | 188 # | | 189 # | 2.1.1 190 # | | \ 191 # | 2.1.2 | 192 # | | | 193 # | | 2.2.1 194 # | | / 195 # | 2.1.3 196 # |/ 197 # 4 198 builder.start_series() 199 self.make_snapshot(builder, None, '1') 200 self.make_snapshot(builder, ['1'], '1.1.1') 201 self.make_snapshot(builder, ['1', '1.1.1'], '2') 202 self.make_snapshot(builder, ['2'], '2.1.1') 203 self.make_snapshot(builder, ['2.1.1'], '2.1.2') 204 self.make_snapshot(builder, ['2.1.1'], '2.2.1') 205 self.make_snapshot(builder, ['2.1.2', '2.2.1'], '2.1.3') 206 self.make_snapshot(builder, ['2'], '3') 207 self.make_snapshot(builder, ['3', '2.1.3'], '4') 208 builder.finish_series() 209 br = builder.get_branch() 210 br.lock_read() 211 self.addCleanup(br.unlock) 212 return br 213 214 def make_branch_with_different_depths_merges(self, relpath='.'): 215 builder = self.make_branch_builder(relpath) 216 # 1 217 # |\ 218 # | 1.1.1 219 # | / 220 # 2 221 # | \ 222 # 3 | 223 # | | 224 # | 2.1.1 225 # | | \ 226 # | 2.1.2 | 227 # | | | 228 # | | 2.2.1 229 # | | / 230 # | 2.1.3 231 # |/ 232 # 4 233 builder.start_series() 234 self.make_snapshot(builder, None, '1') 235 self.make_snapshot(builder, ['1'], '2') 236 self.make_snapshot(builder, ['1'], '1.1.1') 237 self.make_snapshot(builder, ['1.1.1'], '1.1.2') 238 self.make_snapshot(builder, ['1.1.1'], '1.2.1') 239 self.make_snapshot(builder, ['1.2.1'], '1.2.2') 240 self.make_snapshot(builder, ['1.2.1'], '1.3.1') 241 self.make_snapshot(builder, ['1.3.1'], '1.3.2') 242 self.make_snapshot(builder, ['1.3.1'], '1.4.1') 243 self.make_snapshot(builder, ['1.3.2'], '1.3.3') 244 self.make_snapshot(builder, ['1.2.2', '1.3.3'], '1.2.3') 245 self.make_snapshot(builder, ['2'], '2.1.1') 246 self.make_snapshot(builder, ['2.1.1'], '2.1.2') 247 self.make_snapshot(builder, ['2.1.1'], '2.2.1') 248 self.make_snapshot(builder, ['2.1.2', '2.2.1'], '2.1.3') 249 self.make_snapshot(builder, ['2', '1.2.3'], '3') 250 # .. to bring them all and ... bind them 251 self.make_snapshot(builder, ['3', '2.1.3'], '4') 252 builder.finish_series() 253 br = builder.get_branch() 254 br.lock_read() 255 self.addCleanup(br.unlock) 256 return br 257 258 def make_branch_with_alternate_ancestries(self, relpath='.'): 259 # See test_merge_sorted_exclude_ancestry below for the difference with 260 # bt.test_log.TestLogExcludeAncestry. 261 # make_branch_with_alternate_ancestries and 262 # test_merge_sorted_exclude_ancestry 263 # See the FIXME in assertLogRevnos there too. 264 builder = self.make_branch_builder(relpath) 265 # 1 266 # |\ 267 # | 1.1.1 268 # | /| \ 269 # 2 | | 270 # | | 1.2.1 271 # | | / 272 # | 1.1.2 273 # | / 274 # 3 275 builder.start_series() 276 self.make_snapshot(builder, None, '1') 277 self.make_snapshot(builder, ['1'], '1.1.1') 278 self.make_snapshot(builder, ['1', '1.1.1'], '2') 279 self.make_snapshot(builder, ['1.1.1'], '1.2.1') 280 self.make_snapshot(builder, ['1.1.1', '1.2.1'], '1.1.2') 281 self.make_snapshot(builder, ['2', '1.1.2'], '3') 282 builder.finish_series() 283 br = builder.get_branch() 284 br.lock_read() 285 self.addCleanup(br.unlock) 286 return br 287 288 def assertIterRevids(self, expected, branch, *args, **kwargs): 289 if kwargs.get('stop_revision_id') is not None: 290 kwargs['stop_revision_id'] = self.revids[kwargs['stop_revision_id']] 291 if kwargs.get('start_revision_id') is not None: 292 kwargs['start_revision_id'] = self.revids[kwargs['start_revision_id']] 293 # We don't care about depths and revnos here, only about returning the 294 # right revids. 295 revids = [revid for (revid, depth, revno, eom) in 296 branch.iter_merge_sorted_revisions(*args, **kwargs)] 297 self.assertEqual([self.revids[short] for short in expected], revids) 298 299 def test_merge_sorted_starting_at_embedded_merge(self): 300 branch = self.make_branch_with_embedded_merges() 301 self.assertIterRevids(['4', '2.1.3', '2.2.1', '2.1.2', '2.1.1', 302 '3', '2', '1.1.1', '1'], 303 branch) 304 # 3 and 2.1.2 are not part of 2.2.1 ancestry and should not appear 305 self.assertIterRevids(['2.2.1', '2.1.1', '2', '1.1.1', '1'], 306 branch, start_revision_id='2.2.1', 307 stop_rule='with-merges') 308 309 def test_merge_sorted_with_different_depths_merge(self): 310 branch = self.make_branch_with_different_depths_merges() 311 self.assertIterRevids(['4', '2.1.3', '2.2.1', '2.1.2', '2.1.1', 312 '3', 313 '1.2.3', '1.3.3', '1.3.2', '1.3.1', 314 '1.2.2', '1.2.1', '1.1.1', 315 '2', '1'], 316 branch) 317 # 3 (and its descendants) and 2.1.2 are not part of 2.2.1 ancestry and 318 # should not appear 319 self.assertIterRevids(['2.2.1', '2.1.1', '2', '1'], 320 branch, start_revision_id='2.2.1', 321 stop_rule='with-merges') 322 323 def test_merge_sorted_exclude_ancestry(self): 324 branch = self.make_branch_with_alternate_ancestries() 325 self.assertIterRevids(['3', '1.1.2', '1.2.1', '2', '1.1.1', '1'], 326 branch) 327 # '2' is not part of the ancestry even if merge_sort order will make it 328 # appear before 1.1.1 329 self.assertIterRevids(['1.1.2', '1.2.1'], 330 branch, 331 stop_rule='with-merges-without-common-ancestry', 332 start_revision_id='1.1.2', 333 stop_revision_id='1.1.1') 334