1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005 by Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from bzrlib import bzrdir, repository
22
from bzrlib.branch import Branch
23
from bzrlib.bzrdir import BzrDir
24
from bzrlib.builtins import merge
25
20
import bzrlib.errors
26
from bzrlib.tests import TestCaseWithTransport
27
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
28
21
from bzrlib.tests.test_revision import make_branches
29
22
from bzrlib.trace import mutter
30
from bzrlib.upgrade import Convert
31
from bzrlib.workingtree import WorkingTree
23
from bzrlib.branch import Branch
24
from bzrlib.fetch import greedy_fetch
25
from bzrlib.merge import merge
26
from bzrlib.clone import copy_branch
28
from bzrlib.tests import TestCaseInTempDir
29
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
34
32
def has_revision(branch, revision_id):
35
return branch.repository.has_revision(revision_id)
34
branch.get_revision_xml(revision_id)
36
except bzrlib.errors.NoSuchRevision:
37
39
def fetch_steps(self, br_a, br_b, writable_a):
38
40
"""A foreign test method for testing fetch locally and remotely."""
40
# TODO RBC 20060201 make this a repository test.
41
repo_b = br_b.repository
42
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
43
self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
44
self.assertEquals(len(br_b.revision_history()), 7)
45
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
46
# branch.fetch is not supposed to alter the revision history
47
self.assertEquals(len(br_b.revision_history()), 7)
48
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
50
# fetching the next revision up in sample data copies one revision
51
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
52
self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
43
return Branch.initialize(name)
45
self.assertFalse(has_revision(br_b, br_a.revision_history()[3]))
46
self.assert_(has_revision(br_b, br_a.revision_history()[2]))
47
self.assertEquals(len(br_b.revision_history()), 7)
48
self.assertEquals(greedy_fetch(br_b, br_a, br_a.revision_history()[2])[0], 0)
50
# greedy_fetch is not supposed to alter the revision history
51
self.assertEquals(len(br_b.revision_history()), 7)
52
self.assertFalse(has_revision(br_b, br_a.revision_history()[3]))
54
self.assertEquals(len(br_b.revision_history()), 7)
55
self.assertEquals(greedy_fetch(br_b, br_a, br_a.revision_history()[3])[0], 1)
56
self.assert_(has_revision(br_b, br_a.revision_history()[3]))
53
57
self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
54
self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
58
self.assert_(has_revision(br_a, br_b.revision_history()[5]))
56
60
# When a non-branch ancestor is missing, it should be unlisted...
57
61
# as its not reference from the inventory weave.
58
br_b4 = self.make_branch('br_4')
59
count, failures = br_b4.fetch(br_b)
62
br_b4 = new_branch('br_4')
63
count, failures = greedy_fetch(br_b4, br_b)
60
64
self.assertEqual(count, 7)
61
65
self.assertEqual(failures, [])
63
self.assertEqual(writable_a.fetch(br_b)[0], 1)
64
self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
65
self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
67
self.assertEqual(greedy_fetch(writable_a, br_b)[0], 1)
68
self.assert_(has_revision(br_a, br_b.revision_history()[3]))
69
self.assert_(has_revision(br_a, br_b.revision_history()[4]))
67
br_b2 = self.make_branch('br_b2')
68
self.assertEquals(br_b2.fetch(br_b)[0], 7)
69
self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
70
self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
71
br_b2 = new_branch('br_b2')
72
self.assertEquals(greedy_fetch(br_b2, br_b)[0], 7)
73
self.assert_(has_revision(br_b2, br_b.revision_history()[4]))
74
self.assert_(has_revision(br_b2, br_a.revision_history()[2]))
71
75
self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
73
br_a2 = self.make_branch('br_a2')
74
self.assertEquals(br_a2.fetch(br_a)[0], 9)
75
self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
76
self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
77
self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
77
br_a2 = new_branch('br_a2')
78
self.assertEquals(greedy_fetch(br_a2, br_a)[0], 9)
79
self.assert_(has_revision(br_a2, br_b.revision_history()[4]))
80
self.assert_(has_revision(br_a2, br_a.revision_history()[3]))
81
self.assert_(has_revision(br_a2, br_a.revision_history()[2]))
79
br_a3 = self.make_branch('br_a3')
80
# pulling a branch with no revisions grabs nothing, regardless of
81
# whats in the inventory.
82
self.assertEquals(br_a3.fetch(br_a2)[0], 0)
83
br_a3 = new_branch('br_a3')
84
self.assertEquals(greedy_fetch(br_a3, br_a2)[0], 0)
83
85
for revno in range(4):
85
br_a3.repository.has_revision(br_a.revision_history()[revno]))
86
self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
87
# pull the 3 revisions introduced by a@u-0-3
88
fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
86
self.assertFalse(has_revision(br_a3, br_a.revision_history()[revno]))
87
self.assertEqual(greedy_fetch(br_a3, br_a2, br_a.revision_history()[2])[0], 3)
88
fetched = greedy_fetch(br_a3, br_a2, br_a.revision_history()[3])[0]
89
89
self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
90
90
# InstallFailed should be raised if the branch is missing the revision
91
91
# that was requested.
92
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
92
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
93
94
# InstallFailed should be raised if the branch is missing a revision
94
95
# from its own revision history
95
96
br_a2.append_revision('a-b-c')
96
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
97
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
98
# TODO: jam 20051218 Branch should no longer allow append_revision for revisions
99
# which don't exist. So this test needs to be rewritten
100
# RBC 20060403 the way to do this is to uncommit the revision from the
101
# repository after the commit
103
101
#TODO: test that fetch correctly does reweaving when needed. RBC 20051008
104
# Note that this means - updating the weave when ghosts are filled in to
105
# add the right parents.
108
class TestFetch(TestCaseWithTransport):
103
class TestFetch(TestCaseInTempDir):
110
105
def test_fetch(self):
111
106
#highest indices a: 5, b: 7
112
107
br_a, br_b = make_branches(self)
113
108
fetch_steps(self, br_a, br_b, br_a)
115
def test_fetch_self(self):
116
wt = self.make_branch_and_tree('br')
117
self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
119
def test_fetch_root_knit(self):
120
"""Ensure that knit2.fetch() updates the root knit
122
This tests the case where the root has a new revision, but there are no
123
corresponding filename, parent, contents or other changes.
125
knit1_format = bzrdir.BzrDirMetaFormat1()
126
knit1_format.repository_format = repository.RepositoryFormatKnit1()
127
knit2_format = bzrdir.BzrDirMetaFormat1()
128
knit2_format.repository_format = repository.RepositoryFormatKnit2()
129
# we start with a knit1 repository because that causes the
130
# root revision to change for each commit, even though the content,
131
# parent, name, and other attributes are unchanged.
132
tree = self.make_branch_and_tree('tree', knit1_format)
133
tree.set_root_id('tree-root')
134
tree.commit('rev1', rev_id='rev1')
135
tree.commit('rev2', rev_id='rev2')
137
# Now we convert it to a knit2 repository so that it has a root knit
138
Convert(tree.basedir, knit2_format)
139
tree = WorkingTree.open(tree.basedir)
140
branch = self.make_branch('branch', format=knit2_format)
141
branch.pull(tree.branch, stop_revision='rev1')
142
repo = branch.repository
143
root_knit = repo.weave_store.get_weave('tree-root',
144
repo.get_transaction())
145
# Make sure fetch retrieved only what we requested
146
self.assertTrue('rev1' in root_knit)
147
self.assertTrue('rev2' not in root_knit)
148
branch.pull(tree.branch)
149
root_knit = repo.weave_store.get_weave('tree-root',
150
repo.get_transaction())
151
# Make sure that the next revision in the root knit was retrieved,
152
# even though the text, name, parent_id, etc., were unchanged.
153
self.assertTrue('rev2' in root_knit)
156
class TestMergeFetch(TestCaseWithTransport):
111
class TestMergeFetch(TestCaseInTempDir):
158
113
def test_merge_fetches_unrelated(self):
159
114
"""Merge brings across history from unrelated source"""
160
wt1 = self.make_branch_and_tree('br1')
162
wt1.commit(message='rev 1-1', rev_id='1-1')
163
wt1.commit(message='rev 1-2', rev_id='1-2')
164
wt2 = self.make_branch_and_tree('br2')
166
wt2.commit(message='rev 2-1', rev_id='2-1')
116
br1 = Branch.initialize('br1')
117
br1.working_tree().commit(message='rev 1-1', rev_id='1-1')
118
br1.working_tree().commit(message='rev 1-2', rev_id='1-2')
120
br2 = Branch.initialize('br2')
121
br2.working_tree().commit(message='rev 2-1', rev_id='2-1')
167
122
merge(other_revision=['br1', -1], base_revision=['br1', 0],
169
124
self._check_revs_present(br2)
171
126
def test_merge_fetches(self):
172
127
"""Merge brings across history from source"""
173
wt1 = self.make_branch_and_tree('br1')
175
wt1.commit(message='rev 1-1', rev_id='1-1')
176
dir_2 = br1.bzrdir.sprout('br2')
177
br2 = dir_2.open_branch()
178
wt1.commit(message='rev 1-2', rev_id='1-2')
179
dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
129
br1 = Branch.initialize('br1')
130
br1.working_tree().commit(message='rev 1-1', rev_id='1-1')
131
copy_branch(br1, 'br2')
132
br2 = Branch.open('br2')
133
br1.working_tree().commit(message='rev 1-2', rev_id='1-2')
134
br2.working_tree().commit(message='rev 2-1', rev_id='2-1')
180
135
merge(other_revision=['br1', -1], base_revision=[None, None],
182
137
self._check_revs_present(br2)
184
139
def _check_revs_present(self, br2):
185
140
for rev_id in '1-1', '1-2', '2-1':
186
self.assertTrue(br2.repository.has_revision(rev_id))
187
rev = br2.repository.get_revision(rev_id)
141
self.assertTrue(br2.has_revision(rev_id))
142
rev = br2.get_revision(rev_id)
188
143
self.assertEqual(rev.revision_id, rev_id)
189
self.assertTrue(br2.repository.get_inventory(rev_id))
192
class TestMergeFileHistory(TestCaseWithTransport):
144
self.assertTrue(br2.get_inventory(rev_id))
148
class TestMergeFileHistory(TestCaseInTempDir):
195
super(TestMergeFileHistory, self).setUp()
196
wt1 = self.make_branch_and_tree('br1')
150
TestCaseInTempDir.setUp(self)
152
br1 = Branch.initialize('br1')
198
153
self.build_tree_contents([('br1/file', 'original contents\n')])
199
wt1.add('file', 'this-file-id')
200
wt1.commit(message='rev 1-1', rev_id='1-1')
201
dir_2 = br1.bzrdir.sprout('br2')
202
br2 = dir_2.open_branch()
203
wt2 = dir_2.open_workingtree()
154
br1.working_tree().add(['file'], ['this-file-id'])
155
br1.working_tree().commit(message='rev 1-1', rev_id='1-1')
156
copy_branch(br1, 'br2')
157
br2 = Branch.open('br2')
204
158
self.build_tree_contents([('br1/file', 'original from 1\n')])
205
wt1.commit(message='rev 1-2', rev_id='1-2')
159
br1.working_tree().commit(message='rev 1-2', rev_id='1-2')
206
160
self.build_tree_contents([('br1/file', 'agreement\n')])
207
wt1.commit(message='rev 1-3', rev_id='1-3')
161
br1.working_tree().commit(message='rev 1-3', rev_id='1-3')
208
162
self.build_tree_contents([('br2/file', 'contents in 2\n')])
209
wt2.commit(message='rev 2-1', rev_id='2-1')
163
br2.working_tree().commit(message='rev 2-1', rev_id='2-1')
210
164
self.build_tree_contents([('br2/file', 'agreement\n')])
211
wt2.commit(message='rev 2-2', rev_id='2-2')
165
br2.working_tree().commit(message='rev 2-2', rev_id='2-2')
213
167
def test_merge_fetches_file_history(self):
214
168
"""Merge brings across file histories"""
219
173
('1-3', 'agreement\n'),
220
174
('2-1', 'contents in 2\n'),
221
175
('2-2', 'agreement\n')]:
222
self.assertEqualDiff(
223
br2.repository.revision_tree(
224
rev_id).get_file_text('this-file-id'), text)
176
self.assertEqualDiff(br2.revision_tree(rev_id).get_file_text('this-file-id'),
227
182
class TestHttpFetch(TestCaseWithWebserver):
228
# FIXME RBC 20060124 this really isn't web specific, perhaps an
229
# instrumented readonly transport? Can we do an instrumented
230
# adapter and use self.get_readonly_url ?
185
super(TestHttpFetch, self).setUp()
232
188
def test_fetch(self):
233
189
#highest indices a: 5, b: 7
234
190
br_a, br_b = make_branches(self)
235
br_rem_a = Branch.open(self.get_readonly_url('branch1'))
191
br_rem_a = Branch.open(self.get_remote_url(br_a._transport.base))
236
192
fetch_steps(self, br_rem_a, br_b, br_a)
238
def _count_log_matches(self, target, logs):
239
"""Count the number of times the target file pattern was fetched in an http log"""
240
get_succeeds_re = re.compile(
241
'.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
242
( target, bzrlib.__version__))
245
if get_succeeds_re.match(line):
194
def log(self, *args):
195
"""Capture web server log messages for introspection."""
196
super(TestHttpFetch, self).log(*args)
197
# if this call indicates a url being fetched, save it specially
198
if args[0].startswith("webserver"):
199
self.weblogs.append(args[3])
249
201
def test_weaves_are_retrieved_once(self):
250
202
self.build_tree(("source/", "source/file", "target/"))
251
wt = self.make_branch_and_tree('source')
253
wt.add(["file"], ["id"])
254
wt.commit("added file")
203
branch = Branch.initialize("source")
204
branch.working_tree().add(["file"], ["id"])
205
branch.working_tree().commit("added file")
255
206
print >>open("source/file", 'w'), "blah"
256
wt.commit("changed file")
257
target = BzrDir.create_branch_and_repo("target/")
258
source = Branch.open(self.get_readonly_url("source/"))
259
self.assertEqual(target.fetch(source), (2, []))
207
branch.working_tree().commit("changed file")
208
target = Branch.initialize("target/")
209
source = Branch.open(self.get_remote_url("source/"))
210
self.assertEqual(greedy_fetch(target, source), (2, []))
260
211
# this is the path to the literal file. As format changes
261
212
# occur it needs to be updated. FIXME: ask the store for the
263
self.log("web server logs are:")
264
http_logs = self.get_readonly_server().logs
265
self.log('\n'.join(http_logs))
266
# unfortunately this log entry is branch format specific. We could
267
# factor out the 'what files does this format use' to a method on the
268
# repository, which would let us to this generically. RBC 20060419
269
self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
270
self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
271
self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
214
weave_suffix = 'weaves/ce/id.weave HTTP/1.1" 200 -'
216
len([log for log in self.weblogs if log.endswith(weave_suffix)]))
217
inventory_weave_suffix = 'inventory.weave HTTP/1.1" 200 -'
219
len([log for log in self.weblogs if log.endswith(
220
inventory_weave_suffix)]))
272
221
# this r-h check test will prevent regressions, but it currently already
273
222
# passes, before the patch to cache-rh is applied :[
274
self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
275
# FIXME naughty poking in there.
276
self.get_readonly_server().logs = []
223
revision_history_suffix = 'revision-history HTTP/1.1" 200 -'
225
len([log for log in self.weblogs if log.endswith(
226
revision_history_suffix)]))
277
228
# check there is nothing more to fetch
278
source = Branch.open(self.get_readonly_url("source/"))
279
self.assertEqual(target.fetch(source), (0, []))
280
# should make just two requests
281
http_logs = self.get_readonly_server().logs
282
self.log("web server logs are:")
283
self.log('\n'.join(http_logs))
284
self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
285
self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
286
self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
287
self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
288
self.assertEqual(4, len(http_logs))
229
source = Branch.open(self.get_remote_url("source/"))
230
self.assertEqual(greedy_fetch(target, source), (0, []))
231
self.failUnless(self.weblogs[0].endswith('branch-format HTTP/1.1" 200 -'))
232
self.failUnless(self.weblogs[1].endswith('revision-history HTTP/1.1" 200 -'))
233
self.assertEqual(2, len(self.weblogs))