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