20
from bzrlib.selftest import TestCaseInTempDir
21
20
from bzrlib.branch import Branch
21
from bzrlib.errors import NoSuchRevision
22
22
from bzrlib.commit import commit
23
from bzrlib.fetch import fetch
24
from bzrlib.revision import (find_present_ancestors, common_ancestor,
23
from bzrlib.graph import Graph
24
from bzrlib.revision import (find_present_ancestors, combined_graph,
26
is_ancestor, MultipleRevisionSources)
27
from bzrlib.tests import TestCaseWithTransport
26
28
from bzrlib.trace import mutter
27
from bzrlib.errors import NoSuchRevision
31
br1 = Branch("branch1", init=True)
29
from bzrlib.workingtree import WorkingTree
31
# XXX: Make this a method of a merge base case
32
def make_branches(self):
33
"""Create two branches
35
branch 1 has 6 commits, branch 2 has 3 commits
36
commit 10 is a ghosted merge merge from branch 1
48
so A is missing b6 at the start
49
and B is missing a3, a4, a5
51
tree1 = self.make_branch_and_tree("branch1")
33
commit(br1, "Commit one", rev_id="a@u-0-0")
34
commit(br1, "Commit two", rev_id="a@u-0-1")
35
commit(br1, "Commit three", rev_id="a@u-0-2")
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")
38
br2 = Branch("branch2", init=True)
39
br2.update_revisions(br1)
40
commit(br2, "Commit four", rev_id="b@u-0-3")
41
commit(br2, "Commit five", rev_id="b@u-0-4")
58
tree2 = tree1.bzrdir.clone("branch2").open_workingtree()
60
tree2.commit("Commit four", rev_id="b@u-0-3")
61
tree2.commit("Commit five", rev_id="b@u-0-4")
42
62
revisions_2 = br2.revision_history()
44
fetch(from_branch=br2, to_branch=br1)
45
br1.add_pending_merge(revisions_2[4])
46
assert revisions_2[4] == 'b@u-0-4'
47
commit(br1, "Commit six", rev_id="a@u-0-3")
48
commit(br1, "Commit seven", rev_id="a@u-0-4")
49
commit(br2, "Commit eight", rev_id="b@u-0-5")
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")
51
fetch(from_branch=br2, to_branch=br1)
52
br1.add_pending_merge(br2.revision_history()[5])
53
commit(br1, "Commit nine", rev_id="a@u-0-5")
55
fetch(from_branch=br1, to_branch=br2)
56
br2.add_pending_merge(br1.revision_history()[4])
57
commit(br2, "Commit ten", rev_id="b@u-0-6")
59
fetch(from_branch=br2, to_branch=br1)
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.
76
tree2.add_pending_merge(br1.revision_history()[4])
77
tree2.commit("Commit ten - ghost merge", rev_id="b@u-0-6")
64
class TestIsAncestor(TestCaseInTempDir):
82
class TestIsAncestor(TestCaseWithTransport):
65
84
def test_recorded_ancestry(self):
66
85
"""Test that commit records all ancestors"""
67
br1, br2 = make_branches()
86
br1, br2 = make_branches(self)
68
87
d = [('a@u-0-0', ['a@u-0-0']),
69
88
('a@u-0-1', ['a@u-0-0', 'a@u-0-1']),
70
89
('a@u-0-2', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2']),
80
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',
81
100
'b@u-0-3', 'b@u-0-4',
82
101
'b@u-0-5', 'a@u-0-5']),
83
('b@u-0-6', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2', 'a@u-0-3', 'a@u-0-4',
102
('b@u-0-6', ['a@u-0-0', 'a@u-0-1', 'a@u-0-2',
84
103
'b@u-0-3', 'b@u-0-4',
85
104
'b@u-0-5', 'b@u-0-6']),
106
br1_only = ('a@u-0-3', 'a@u-0-4', 'a@u-0-5')
107
br2_only = ('b@u-0-6',)
87
108
for branch in br1, br2:
88
109
for rev_id, anc in d:
110
if rev_id in br1_only and not branch is br1:
112
if rev_id in br2_only and not branch is br2:
89
114
mutter('ancestry of {%s}: %r',
90
rev_id, branch.get_ancestry(rev_id))
91
self.assertEquals(sorted(branch.get_ancestry(rev_id)),
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))
95
120
def test_is_ancestor(self):
96
121
"""Test checking whether a revision is an ancestor of another revision"""
97
br1, br2 = make_branches()
122
br1, br2 = make_branches(self)
98
123
revisions = br1.revision_history()
99
124
revisions_2 = br2.revision_history()
102
assert is_ancestor(revisions[0], revisions[0], br1)
103
assert is_ancestor(revisions[1], revisions[0], sources)
104
assert not is_ancestor(revisions[0], revisions[1], sources)
105
assert is_ancestor(revisions_2[3], revisions[0], sources)
106
# disabled mbp 20050914, doesn't seem to happen anymore
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
107
132
## self.assertRaises(NoSuchRevision, is_ancestor, revisions_2[3],
108
133
## revisions[0], br1)
109
assert is_ancestor(revisions[3], revisions_2[4], sources)
110
assert is_ancestor(revisions[3], revisions_2[4], br1)
111
assert is_ancestor(revisions[3], revisions_2[3], sources)
112
## assert not is_ancestor(revisions[3], revisions_2[3], br1)
116
class TestIntermediateRevisions(TestCaseInTempDir):
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))
140
class TestIntermediateRevisions(TestCaseWithTransport):
119
143
from bzrlib.commit import commit
120
TestCaseInTempDir.setUp(self)
121
self.br1, self.br2 = make_branches()
123
self.br2.commit("Commit eleven", rev_id="b@u-0-7")
124
self.br2.commit("Commit twelve", rev_id="b@u-0-8")
125
self.br2.commit("Commit thirtteen", rev_id="b@u-0-9")
127
fetch(from_branch=self.br2, to_branch=self.br1)
128
self.br1.add_pending_merge(self.br2.revision_history()[6])
129
self.br1.commit("Commit fourtten", rev_id="a@u-0-6")
131
fetch(from_branch=self.br1, to_branch=self.br2)
132
self.br2.add_pending_merge(self.br1.revision_history()[6])
133
self.br2.commit("Commit fifteen", rev_id="b@u-0-10")
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")
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")
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")
135
160
from bzrlib.revision import MultipleRevisionSources
136
self.sources = MultipleRevisionSources(self.br1, self.br2)
138
def intervene(self, ancestor, revision, revision_history=None):
139
from bzrlib.revision import get_intervening_revisions
140
return get_intervening_revisions(ancestor,revision, self.sources,
143
def test_intervene(self):
144
"""Find intermediate revisions, without requiring history"""
145
from bzrlib.errors import NotAncestor, NoSuchRevision
146
assert len(self.intervene('a@u-0-0', 'a@u-0-0')) == 0
147
self.assertEqual(self.intervene('a@u-0-0', 'a@u-0-1'), ['a@u-0-1'])
148
self.assertEqual(self.intervene('a@u-0-0', 'a@u-0-2'),
149
['a@u-0-1', 'a@u-0-2'])
150
self.assertEqual(self.intervene('a@u-0-0', 'b@u-0-3'),
151
['a@u-0-1', 'a@u-0-2', 'b@u-0-3'])
152
self.assertEqual(self.intervene('b@u-0-3', 'a@u-0-3'),
153
['b@u-0-4', 'a@u-0-3'])
154
self.assertEqual(self.intervene('a@u-0-2', 'a@u-0-3',
155
self.br1.revision_history()),
157
self.assertEqual(self.intervene('a@u-0-0', 'a@u-0-5',
158
self.br1.revision_history()),
159
['a@u-0-1', 'a@u-0-2', 'a@u-0-3', 'a@u-0-4',
161
self.assertEqual(self.intervene('a@u-0-0', 'b@u-0-6',
162
self.br1.revision_history()),
163
['a@u-0-1', 'a@u-0-2', 'a@u-0-3', 'a@u-0-4',
165
self.assertEqual(self.intervene('a@u-0-0', 'b@u-0-5'),
166
['a@u-0-1', 'a@u-0-2', 'b@u-0-3', 'b@u-0-4',
168
self.assertEqual(self.intervene('b@u-0-3', 'b@u-0-6',
169
self.br2.revision_history()),
170
['b@u-0-4', 'b@u-0-5', 'b@u-0-6'])
171
self.assertEqual(self.intervene('b@u-0-6', 'b@u-0-10'),
172
['b@u-0-7', 'b@u-0-8', 'b@u-0-9', 'b@u-0-10'])
173
self.assertEqual(self.intervene('b@u-0-6', 'b@u-0-10',
174
self.br2.revision_history()),
175
['b@u-0-7', 'b@u-0-8', 'b@u-0-9', 'b@u-0-10'])
176
self.assertRaises(NotAncestor, self.intervene, 'b@u-0-10', 'b@u-0-6',
177
self.br2.revision_history())
178
self.assertRaises(NoSuchRevision, self.intervene, 'c@u-0-10',
179
'b@u-0-6', self.br2.revision_history())
180
self.assertRaises(NoSuchRevision, self.intervene, 'b@u-0-10',
181
'c@u-0-6', self.br2.revision_history())
184
class TestCommonAncestor(TestCaseInTempDir):
161
self.sources = MultipleRevisionSources(self.br1.repository,
166
class MockRevisionSource(object):
167
"""A RevisionSource that takes a pregenerated graph.
169
This is useful for testing revision graph algorithms where
170
the actual branch existing is irrelevant.
173
def __init__(self, full_graph):
174
self._full_graph = full_graph
176
def get_revision_graph_with_ghosts(self, revision_ids):
177
# This is mocked out to just return a constant graph.
178
return self._full_graph
181
class TestCommonAncestor(TestCaseWithTransport):
185
182
"""Test checking whether a revision is an ancestor of another revision"""
187
184
def test_common_ancestor(self):
188
br1, br2 = make_branches()
185
"""Pick a reasonable merge base"""
186
br1, br2 = make_branches(self)
189
187
revisions = br1.revision_history()
190
188
revisions_2 = br2.revision_history()
189
sources = MultipleRevisionSources(br1.repository, br2.repository)
193
190
expected_ancestors_list = {revisions[3]:(0, 0),
194
191
revisions[2]:(1, 1),
195
192
revisions_2[4]:(2, 1),
216
212
self.assertEqual(common_ancestor(revisions[4], revisions_2[5], sources),
218
self.assertEqual(common_ancestor(revisions[5], revisions_2[6], sources),
220
self.assertEqual(common_ancestor(revisions_2[6], revisions[5], sources),
224
if __name__ == '__main__':
214
self.assertTrue(common_ancestor(revisions[5], revisions_2[6], sources) in
215
(revisions[4], revisions_2[5]))
216
self.assertTrue(common_ancestor(revisions_2[6], revisions[5], sources),
217
(revisions[4], revisions_2[5]))
218
self.assertEqual(None, common_ancestor(None, revisions[5], sources))
220
def test_combined(self):
222
Ensure it's not order-sensitive
224
br1, br2 = make_branches(self)
225
source = MultipleRevisionSources(br1.repository, br2.repository)
226
combined_1 = combined_graph(br1.last_revision(),
227
br2.last_revision(), source)
228
combined_2 = combined_graph(br2.last_revision(),
229
br1.last_revision(), source)
230
self.assertEquals(combined_1[1], combined_2[1])
231
self.assertEquals(combined_1[2], combined_2[2])
232
self.assertEquals(combined_1[3], combined_2[3])
233
self.assertEquals(combined_1, combined_2)
235
def test_get_history(self):
236
# TODO: test ghosts on the left hand branch's impact
237
# TODO: test ghosts on all parents, we should get some
238
# indicator. i.e. NULL_REVISION
240
tree = self.make_branch_and_tree('.')
241
tree.commit('1', rev_id = '1', allow_pointless=True)
242
tree.commit('2', rev_id = '2', allow_pointless=True)
243
tree.commit('3', rev_id = '3', allow_pointless=True)
244
rev = tree.branch.repository.get_revision('1')
245
history = rev.get_history(tree.branch.repository)
246
self.assertEqual([None, '1'], history)
247
rev = tree.branch.repository.get_revision('2')
248
history = rev.get_history(tree.branch.repository)
249
self.assertEqual([None, '1', '2'], history)
250
rev = tree.branch.repository.get_revision('3')
251
history = rev.get_history(tree.branch.repository)
252
self.assertEqual([None, '1', '2' ,'3'], history)
254
def test_common_ancestor_rootless_graph(self):
255
# common_ancestor on a graph with no reachable roots - only
256
# ghosts - should still return a useful value.
258
# add a ghost node which would be a root if it wasn't a ghost.
259
graph.add_ghost('a_ghost')
260
# add a normal commit on top of that
261
graph.add_node('rev1', ['a_ghost'])
262
# add a left-branch revision
263
graph.add_node('left', ['rev1'])
264
# add a right-branch revision
265
graph.add_node('right', ['rev1'])
266
source = MockRevisionSource(graph)
267
self.assertEqual('rev1', common_ancestor('left', 'right', source))
270
class TestMultipleRevisionSources(TestCaseWithTransport):
271
"""Tests for the MultipleRevisionSources adapter."""
273
def test_get_revision_graph_merges_ghosts(self):
274
# when we ask for the revision graph for B, which
275
# is in repo 1 with a ghost of A, and which is not
276
# in repo 2, which has A, the revision_graph()
277
# should return A and B both.
278
tree_1 = self.make_branch_and_tree('1')
279
tree_1.add_pending_merge('A')
280
tree_1.commit('foo', rev_id='B', allow_pointless=True)
281
tree_2 = self.make_branch_and_tree('2')
282
tree_2.commit('bar', rev_id='A', allow_pointless=True)
283
source = MultipleRevisionSources(tree_1.branch.repository,
284
tree_2.branch.repository)
285
self.assertEqual({'B':['A'],
287
source.get_revision_graph('B'))