~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_revision.py

  • Committer: Martin Pool
  • Date: 2005-05-03 07:48:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050503074854-adb6f9d6382e27a9
- sketchy experiments in bash and zsh completion

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
import os
19
 
 
20
 
from bzrlib.branch import Branch
21
 
from bzrlib.errors import NoSuchRevision
22
 
from bzrlib.commit import commit
23
 
from bzrlib.graph import Graph
24
 
from bzrlib.revision import (find_present_ancestors, combined_graph,
25
 
                             common_ancestor,
26
 
                             is_ancestor, MultipleRevisionSources)
27
 
from bzrlib.tests import TestCaseWithTransport
28
 
from bzrlib.trace import mutter
29
 
from bzrlib.workingtree import WorkingTree
30
 
 
31
 
# XXX: Make this a method of a merge base case
32
 
def make_branches(self):
33
 
    """Create two branches
34
 
 
35
 
    branch 1 has 6 commits, branch 2 has 3 commits
36
 
    commit 10 is a ghosted merge merge from branch 1
37
 
 
38
 
    the object graph is
39
 
    B:     A:
40
 
    a..0   a..0 
41
 
    a..1   a..1
42
 
    a..2   a..2
43
 
    b..3   a..3 merges b..4
44
 
    b..4   a..4
45
 
    b..5   a..5 merges b..5
46
 
    b..6 merges a4
47
 
 
48
 
    so A is missing b6 at the start
49
 
    and B is missing a3, a4, a5
50
 
    """
51
 
    tree1 = self.make_branch_and_tree("branch1")
52
 
    br1 = tree1.branch
53
 
    
54
 
    tree1.commit("Commit one", rev_id="a@u-0-0")
55
 
    tree1.commit("Commit two", rev_id="a@u-0-1")
56
 
    tree1.commit("Commit three", rev_id="a@u-0-2")
57
 
 
58
 
    tree2 = tree1.bzrdir.clone("branch2").open_workingtree()
59
 
    br2 = tree2.branch
60
 
    tree2.commit("Commit four", rev_id="b@u-0-3")
61
 
    tree2.commit("Commit five", rev_id="b@u-0-4")
62
 
    revisions_2 = br2.revision_history()
63
 
    
64
 
    br1.fetch(br2)
65
 
    tree1.add_pending_merge(revisions_2[4])
66
 
    self.assertEquals(revisions_2[4], 'b@u-0-4')
67
 
    tree1.commit("Commit six", rev_id="a@u-0-3")
68
 
    tree1.commit("Commit seven", rev_id="a@u-0-4")
69
 
    tree2.commit("Commit eight", rev_id="b@u-0-5")
70
 
    
71
 
    br1.fetch(br2)
72
 
    tree1.add_pending_merge(br2.revision_history()[5])
73
 
    tree1.commit("Commit nine", rev_id="a@u-0-5")
74
 
    # DO NOT FETCH HERE - we WANT a GHOST.
75
 
    # br2.fetch(br1)
76
 
    tree2.add_pending_merge(br1.revision_history()[4])
77
 
    tree2.commit("Commit ten - ghost merge", rev_id="b@u-0-6")
78
 
    
79
 
    return br1, br2
80
 
 
81
 
 
82
 
class TestIsAncestor(TestCaseWithTransport):
83
 
 
84
 
    def test_recorded_ancestry(self):
85
 
        """Test that commit records all ancestors"""
86
 
        br1, br2 = make_branches(self)
87
 
        d = [('a@u-0-0', ['a@u-0-0']),
88
 
             ('a@u-0-1', ['a@u-0-0', 'a@u-0-1']),
89
 
             ('a@u-0-2', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2']),
90
 
             ('b@u-0-3', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'b@u-0-3']),
91
 
             ('b@u-0-4', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'b@u-0-3',
92
 
                          'b@u-0-4']),
93
 
             ('a@u-0-3', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'b@u-0-3', 'b@u-0-4',
94
 
                          'a@u-0-3']),
95
 
             ('a@u-0-4', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'b@u-0-3', 'b@u-0-4',
96
 
                          'a@u-0-3', 'a@u-0-4']),
97
 
             ('b@u-0-5', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'b@u-0-3', 'b@u-0-4',
98
 
                          'b@u-0-5']),
99
 
             ('a@u-0-5', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'a@u-0-3', 'a@u-0-4',
100
 
                          'b@u-0-3', 'b@u-0-4',
101
 
                          'b@u-0-5', 'a@u-0-5']),
102
 
             ('b@u-0-6', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2',
103
 
                          'b@u-0-3', 'b@u-0-4',
104
 
                          'b@u-0-5', 'b@u-0-6']),
105
 
             ]
106
 
        br1_only = ('a@u-0-3', 'a@u-0-4', 'a@u-0-5')
107
 
        br2_only = ('b@u-0-6',)
108
 
        for branch in br1, br2:
109
 
            for rev_id, anc in d:
110
 
                if rev_id in br1_only and not branch is br1:
111
 
                    continue
112
 
                if rev_id in br2_only and not branch is br2:
113
 
                    continue
114
 
                mutter('ancestry of {%s}: %r',
115
 
                       rev_id, branch.repository.get_ancestry(rev_id))
116
 
                result = sorted(branch.repository.get_ancestry(rev_id))
117
 
                self.assertEquals(result, [None] + sorted(anc))
118
 
    
119
 
    
120
 
    def test_is_ancestor(self):
121
 
        """Test checking whether a revision is an ancestor of another revision"""
122
 
        br1, br2 = make_branches(self)
123
 
        revisions = br1.revision_history()
124
 
        revisions_2 = br2.revision_history()
125
 
        sources = br1
126
 
 
127
 
        self.assert_(is_ancestor(revisions[0], revisions[0], br1))
128
 
        self.assert_(is_ancestor(revisions[1], revisions[0], sources))
129
 
        self.assert_(not is_ancestor(revisions[0], revisions[1], sources))
130
 
        self.assert_(is_ancestor(revisions_2[3], revisions[0], sources))
131
 
        # disabled mbp 20050914, doesn't seem to happen anymore
132
 
        ## self.assertRaises(NoSuchRevision, is_ancestor, revisions_2[3],
133
 
        ##                  revisions[0], br1)        
134
 
        self.assert_(is_ancestor(revisions[3], revisions_2[4], sources))
135
 
        self.assert_(is_ancestor(revisions[3], revisions_2[4], br1))
136
 
        self.assert_(is_ancestor(revisions[3], revisions_2[3], sources))
137
 
        ## self.assert_(not is_ancestor(revisions[3], revisions_2[3], br1))
138
 
 
139
 
 
140
 
class TestIntermediateRevisions(TestCaseWithTransport):
141
 
 
142
 
    def setUp(self):
143
 
        from bzrlib.commit import commit
144
 
        TestCaseWithTransport.setUp(self)
145
 
        self.br1, self.br2 = make_branches(self)
146
 
        wt1 = self.br1.bzrdir.open_workingtree()
147
 
        wt2 = self.br2.bzrdir.open_workingtree()
148
 
        wt2.commit("Commit eleven", rev_id="b@u-0-7")
149
 
        wt2.commit("Commit twelve", rev_id="b@u-0-8")
150
 
        wt2.commit("Commit thirtteen", rev_id="b@u-0-9")
151
 
 
152
 
        self.br1.fetch(self.br2)
153
 
        wt1.add_pending_merge(self.br2.revision_history()[6])
154
 
        wt1.commit("Commit fourtten", rev_id="a@u-0-6")
155
 
 
156
 
        self.br2.fetch(self.br1)
157
 
        wt2.add_pending_merge(self.br1.revision_history()[6])
158
 
        wt2.commit("Commit fifteen", rev_id="b@u-0-10")
159
 
 
160
 
        from bzrlib.revision import MultipleRevisionSources
161
 
        self.sources = MultipleRevisionSources(self.br1.repository,
162
 
                                               self.br2.repository)
163
 
 
164
 
    def intervene(self, ancestor, revision, revision_history=None):
165
 
        from bzrlib.revision import get_intervening_revisions
166
 
        return get_intervening_revisions(ancestor,revision, self.sources, 
167
 
                                         revision_history)
168
 
 
169
 
    def test_intervene(self):
170
 
        """Find intermediate revisions, without requiring history"""
171
 
        from bzrlib.errors import NotAncestor, NoSuchRevision
172
 
        self.assertEquals(len(self.intervene('a@u-0-0', 'a@u-0-0')), 0)
173
 
        self.assertEqual(self.intervene('a@u-0-0', 'a@u-0-1'), ['a@u-0-1'])
174
 
        self.assertEqual(self.intervene('a@u-0-0', 'a@u-0-2'), 
175
 
                         ['a@u-0-1', 'a@u-0-2'])
176
 
        self.assertEqual(self.intervene('a@u-0-0', 'b@u-0-3'), 
177
 
                         ['a@u-0-1', 'a@u-0-2', 'b@u-0-3'])
178
 
        self.assertEqual(self.intervene('b@u-0-3', 'a@u-0-3'), 
179
 
                         ['b@u-0-4', 'a@u-0-3'])
180
 
        self.assertEqual(self.intervene('a@u-0-2', 'a@u-0-3', 
181
 
                                        self.br1.revision_history()), 
182
 
                         ['a@u-0-3'])
183
 
        self.assertEqual(self.intervene('a@u-0-0', 'a@u-0-5', 
184
 
                                        self.br1.revision_history()), 
185
 
                         ['a@u-0-1', 'a@u-0-2', 'a@u-0-3', 'a@u-0-4', 
186
 
                          'a@u-0-5'])
187
 
        self.assertEqual(self.intervene('a@u-0-0', 'b@u-0-6', 
188
 
                         self.br1.revision_history()), 
189
 
                         ['a@u-0-1', 'a@u-0-2', 'a@u-0-3', 'a@u-0-4', 
190
 
                          'b@u-0-6'])
191
 
        self.assertEqual(self.intervene('a@u-0-0', 'b@u-0-5'), 
192
 
                         ['a@u-0-1', 'a@u-0-2', 'b@u-0-3', 'b@u-0-4', 
193
 
                          'b@u-0-5'])
194
 
        self.assertEqual(self.intervene('b@u-0-3', 'b@u-0-6', 
195
 
                         self.br2.revision_history()), 
196
 
                         ['b@u-0-4', 'b@u-0-5', 'b@u-0-6'])
197
 
        self.assertEqual(self.intervene('b@u-0-6', 'b@u-0-10'), 
198
 
                         ['b@u-0-7', 'b@u-0-8', 'b@u-0-9', 'b@u-0-10'])
199
 
        self.assertEqual(self.intervene('b@u-0-6', 'b@u-0-10', 
200
 
                                        self.br2.revision_history()), 
201
 
                         ['b@u-0-7', 'b@u-0-8', 'b@u-0-9', 'b@u-0-10'])
202
 
        self.assertRaises(NotAncestor, self.intervene, 'b@u-0-10', 'b@u-0-6', 
203
 
                          self.br2.revision_history())
204
 
        self.assertRaises(NoSuchRevision, self.intervene, 'c@u-0-10', 
205
 
                          'b@u-0-6', self.br2.revision_history())
206
 
        self.assertRaises(NoSuchRevision, self.intervene, 'b@u-0-10', 
207
 
                          'c@u-0-6', self.br2.revision_history())
208
 
 
209
 
 
210
 
class MockRevisionSource(object):
211
 
    """A RevisionSource that takes a pregenerated graph.
212
 
 
213
 
    This is useful for testing revision graph algorithms where
214
 
    the actual branch existing is irrelevant.
215
 
    """
216
 
 
217
 
    def __init__(self, full_graph):
218
 
        self._full_graph = full_graph
219
 
 
220
 
    def get_revision_graph_with_ghosts(self, revision_ids):
221
 
        # This is mocked out to just return a constant graph.
222
 
        return self._full_graph
223
 
 
224
 
 
225
 
class TestCommonAncestor(TestCaseWithTransport):
226
 
    """Test checking whether a revision is an ancestor of another revision"""
227
 
 
228
 
    def test_common_ancestor(self):
229
 
        """Pick a reasonable merge base"""
230
 
        br1, br2 = make_branches(self)
231
 
        revisions = br1.revision_history()
232
 
        revisions_2 = br2.revision_history()
233
 
        sources = MultipleRevisionSources(br1.repository, br2.repository)
234
 
        expected_ancestors_list = {revisions[3]:(0, 0), 
235
 
                                   revisions[2]:(1, 1),
236
 
                                   revisions_2[4]:(2, 1), 
237
 
                                   revisions[1]:(3, 2),
238
 
                                   revisions_2[3]:(4, 2),
239
 
                                   revisions[0]:(5, 3) }
240
 
        ancestors_list = find_present_ancestors(revisions[3], sources)
241
 
        self.assertEquals(len(expected_ancestors_list), len(ancestors_list))
242
 
        for key, value in expected_ancestors_list.iteritems():
243
 
            self.assertEqual(ancestors_list[key], value, 
244
 
                              "key %r, %r != %r" % (key, ancestors_list[key],
245
 
                                                    value))
246
 
        self.assertEqual(common_ancestor(revisions[0], revisions[0], sources),
247
 
                          revisions[0])
248
 
        self.assertEqual(common_ancestor(revisions[1], revisions[2], sources),
249
 
                          revisions[1])
250
 
        self.assertEqual(common_ancestor(revisions[1], revisions[1], sources),
251
 
                          revisions[1])
252
 
        self.assertEqual(common_ancestor(revisions[2], revisions_2[4], sources),
253
 
                          revisions[2])
254
 
        self.assertEqual(common_ancestor(revisions[3], revisions_2[4], sources),
255
 
                          revisions_2[4])
256
 
        self.assertEqual(common_ancestor(revisions[4], revisions_2[5], sources),
257
 
                          revisions_2[4])
258
 
        self.assertTrue(common_ancestor(revisions[5], revisions_2[6], sources) in
259
 
                        (revisions[4], revisions_2[5]))
260
 
        self.assertTrue(common_ancestor(revisions_2[6], revisions[5], sources),
261
 
                        (revisions[4], revisions_2[5]))
262
 
        self.assertEqual(None, common_ancestor(None, revisions[5], sources))
263
 
 
264
 
    def test_combined(self):
265
 
        """combined_graph
266
 
        Ensure it's not order-sensitive
267
 
        """
268
 
        br1, br2 = make_branches(self)
269
 
        source = MultipleRevisionSources(br1.repository, br2.repository)
270
 
        combined_1 = combined_graph(br1.last_revision(), 
271
 
                                    br2.last_revision(), source)
272
 
        combined_2 = combined_graph(br2.last_revision(),
273
 
                                    br1.last_revision(), source)
274
 
        self.assertEquals(combined_1[1], combined_2[1])
275
 
        self.assertEquals(combined_1[2], combined_2[2])
276
 
        self.assertEquals(combined_1[3], combined_2[3])
277
 
        self.assertEquals(combined_1, combined_2)
278
 
 
279
 
    def test_get_history(self):
280
 
        # TODO: test ghosts on the left hand branch's impact
281
 
        # TODO: test ghosts on all parents, we should get some
282
 
        # indicator. i.e. NULL_REVISION
283
 
        # RBC 20060608
284
 
        tree = self.make_branch_and_tree('.')
285
 
        tree.commit('1', rev_id = '1', allow_pointless=True)
286
 
        tree.commit('2', rev_id = '2', allow_pointless=True)
287
 
        tree.commit('3', rev_id = '3', allow_pointless=True)
288
 
        rev = tree.branch.repository.get_revision('1')
289
 
        history = rev.get_history(tree.branch.repository)
290
 
        self.assertEqual([None, '1'], history)
291
 
        rev = tree.branch.repository.get_revision('2')
292
 
        history = rev.get_history(tree.branch.repository)
293
 
        self.assertEqual([None, '1', '2'], history)
294
 
        rev = tree.branch.repository.get_revision('3')
295
 
        history = rev.get_history(tree.branch.repository)
296
 
        self.assertEqual([None, '1', '2' ,'3'], history)
297
 
 
298
 
    def test_common_ancestor_rootless_graph(self):
299
 
        # common_ancestor on a graph with no reachable roots - only
300
 
        # ghosts - should still return a useful value.
301
 
        graph = Graph()
302
 
        # add a ghost node which would be a root if it wasn't a ghost.
303
 
        graph.add_ghost('a_ghost')
304
 
        # add a normal commit on top of that
305
 
        graph.add_node('rev1', ['a_ghost'])
306
 
        # add a left-branch revision
307
 
        graph.add_node('left', ['rev1'])
308
 
        # add a right-branch revision
309
 
        graph.add_node('right', ['rev1'])
310
 
        source = MockRevisionSource(graph)
311
 
        self.assertEqual('rev1', common_ancestor('left', 'right', source))
312
 
 
313
 
 
314
 
class TestMultipleRevisionSources(TestCaseWithTransport):
315
 
    """Tests for the MultipleRevisionSources adapter."""
316
 
 
317
 
    def test_get_revision_graph_merges_ghosts(self):
318
 
        # when we ask for the revision graph for B, which
319
 
        # is in repo 1 with a ghost of A, and which is not
320
 
        # in repo 2, which has A, the revision_graph()
321
 
        # should return A and B both.
322
 
        tree_1 = self.make_branch_and_tree('1')
323
 
        tree_1.add_pending_merge('A')
324
 
        tree_1.commit('foo', rev_id='B', allow_pointless=True)
325
 
        tree_2 = self.make_branch_and_tree('2')
326
 
        tree_2.commit('bar', rev_id='A', allow_pointless=True)
327
 
        source = MultipleRevisionSources(tree_1.branch.repository,
328
 
                                         tree_2.branch.repository)
329
 
        self.assertEqual({'B':['A'],
330
 
                          'A':[]},
331
 
                         source.get_revision_graph('B'))