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
26
from bzrlib.branch import Branch
27
from bzrlib.bzrdir import BzrDir
28
from bzrlib.builtins import merge
29
20
import bzrlib.errors
30
from bzrlib.repofmt import knitrepo
31
from bzrlib.tests import TestCaseWithTransport
32
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
33
21
from bzrlib.tests.test_revision import make_branches
34
22
from bzrlib.trace import mutter
35
from bzrlib.upgrade import Convert
36
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
39
32
def has_revision(branch, revision_id):
40
return branch.repository.has_revision(revision_id)
34
branch.get_revision_xml(revision_id)
36
except bzrlib.errors.NoSuchRevision:
42
39
def fetch_steps(self, br_a, br_b, writable_a):
43
40
"""A foreign test method for testing fetch locally and remotely."""
45
# TODO RBC 20060201 make this a repository test.
46
repo_b = br_b.repository
47
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
48
self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
49
self.assertEquals(len(br_b.revision_history()), 7)
50
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
51
# branch.fetch is not supposed to alter the revision history
52
self.assertEquals(len(br_b.revision_history()), 7)
53
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
55
# fetching the next revision up in sample data copies one revision
56
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
57
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]))
58
57
self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
59
self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
58
self.assert_(has_revision(br_a, br_b.revision_history()[5]))
61
60
# When a non-branch ancestor is missing, it should be unlisted...
62
61
# as its not reference from the inventory weave.
63
br_b4 = self.make_branch('br_4')
64
count, failures = br_b4.fetch(br_b)
62
br_b4 = new_branch('br_4')
63
count, failures = greedy_fetch(br_b4, br_b)
65
64
self.assertEqual(count, 7)
66
65
self.assertEqual(failures, [])
68
self.assertEqual(writable_a.fetch(br_b)[0], 1)
69
self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
70
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]))
72
br_b2 = self.make_branch('br_b2')
73
self.assertEquals(br_b2.fetch(br_b)[0], 7)
74
self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
75
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]))
76
75
self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
78
br_a2 = self.make_branch('br_a2')
79
self.assertEquals(br_a2.fetch(br_a)[0], 9)
80
self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
81
self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
82
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]))
84
br_a3 = self.make_branch('br_a3')
85
# pulling a branch with no revisions grabs nothing, regardless of
86
# whats in the inventory.
87
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)
88
85
for revno in range(4):
90
br_a3.repository.has_revision(br_a.revision_history()[revno]))
91
self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
92
# pull the 3 revisions introduced by a@u-0-3
93
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]
94
89
self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
95
90
# InstallFailed should be raised if the branch is missing the revision
96
91
# that was requested.
97
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
92
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
98
94
# InstallFailed should be raised if the branch is missing a revision
99
95
# from its own revision history
100
96
br_a2.append_revision('a-b-c')
101
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
97
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
103
# TODO: ADHB 20070116 Perhaps set_last_revision shouldn't accept
104
# revisions which are not present? In that case, this test
107
# RBC 20060403 the way to do this is to uncommit the revision from
108
# the repository after the commit
110
101
#TODO: test that fetch correctly does reweaving when needed. RBC 20051008
111
# Note that this means - updating the weave when ghosts are filled in to
112
# add the right parents.
115
class TestFetch(TestCaseWithTransport):
103
class TestFetch(TestCaseInTempDir):
117
105
def test_fetch(self):
118
106
#highest indices a: 5, b: 7
119
107
br_a, br_b = make_branches(self)
120
108
fetch_steps(self, br_a, br_b, br_a)
122
def test_fetch_self(self):
123
wt = self.make_branch_and_tree('br')
124
self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
126
def test_fetch_root_knit(self):
127
"""Ensure that knit2.fetch() updates the root knit
129
This tests the case where the root has a new revision, but there are no
130
corresponding filename, parent, contents or other changes.
132
knit1_format = bzrdir.BzrDirMetaFormat1()
133
knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
134
knit2_format = bzrdir.BzrDirMetaFormat1()
135
knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
136
# we start with a knit1 repository because that causes the
137
# root revision to change for each commit, even though the content,
138
# parent, name, and other attributes are unchanged.
139
tree = self.make_branch_and_tree('tree', knit1_format)
140
tree.set_root_id('tree-root')
141
tree.commit('rev1', rev_id='rev1')
142
tree.commit('rev2', rev_id='rev2')
144
# Now we convert it to a knit2 repository so that it has a root knit
145
Convert(tree.basedir, knit2_format)
146
tree = WorkingTree.open(tree.basedir)
147
branch = self.make_branch('branch', format=knit2_format)
148
branch.pull(tree.branch, stop_revision='rev1')
149
repo = branch.repository
150
root_knit = repo.weave_store.get_weave('tree-root',
151
repo.get_transaction())
152
# Make sure fetch retrieved only what we requested
153
self.assertTrue('rev1' in root_knit)
154
self.assertTrue('rev2' not in root_knit)
155
branch.pull(tree.branch)
156
root_knit = repo.weave_store.get_weave('tree-root',
157
repo.get_transaction())
158
# Make sure that the next revision in the root knit was retrieved,
159
# even though the text, name, parent_id, etc., were unchanged.
160
self.assertTrue('rev2' in root_knit)
162
def test_fetch_incompatible(self):
163
knit_tree = self.make_branch_and_tree('knit', format='knit')
164
knit3_tree = self.make_branch_and_tree('knit3',
165
format='dirstate-with-subtree')
166
knit3_tree.commit('blah')
167
self.assertRaises(errors.IncompatibleRepositories,
168
knit_tree.branch.fetch, knit3_tree.branch)
171
class TestMergeFetch(TestCaseWithTransport):
111
class TestMergeFetch(TestCaseInTempDir):
173
113
def test_merge_fetches_unrelated(self):
174
114
"""Merge brings across history from unrelated source"""
175
wt1 = self.make_branch_and_tree('br1')
177
wt1.commit(message='rev 1-1', rev_id='1-1')
178
wt1.commit(message='rev 1-2', rev_id='1-2')
179
wt2 = self.make_branch_and_tree('br2')
181
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')
182
122
merge(other_revision=['br1', -1], base_revision=['br1', 0],
184
124
self._check_revs_present(br2)
186
126
def test_merge_fetches(self):
187
127
"""Merge brings across history from source"""
188
wt1 = self.make_branch_and_tree('br1')
190
wt1.commit(message='rev 1-1', rev_id='1-1')
191
dir_2 = br1.bzrdir.sprout('br2')
192
br2 = dir_2.open_branch()
193
wt1.commit(message='rev 1-2', rev_id='1-2')
194
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')
195
135
merge(other_revision=['br1', -1], base_revision=[None, None],
197
137
self._check_revs_present(br2)
199
139
def _check_revs_present(self, br2):
200
140
for rev_id in '1-1', '1-2', '2-1':
201
self.assertTrue(br2.repository.has_revision(rev_id))
202
rev = br2.repository.get_revision(rev_id)
141
self.assertTrue(br2.has_revision(rev_id))
142
rev = br2.get_revision(rev_id)
203
143
self.assertEqual(rev.revision_id, rev_id)
204
self.assertTrue(br2.repository.get_inventory(rev_id))
207
class TestMergeFileHistory(TestCaseWithTransport):
144
self.assertTrue(br2.get_inventory(rev_id))
148
class TestMergeFileHistory(TestCaseInTempDir):
210
super(TestMergeFileHistory, self).setUp()
211
wt1 = self.make_branch_and_tree('br1')
150
TestCaseInTempDir.setUp(self)
152
br1 = Branch.initialize('br1')
213
153
self.build_tree_contents([('br1/file', 'original contents\n')])
214
wt1.add('file', 'this-file-id')
215
wt1.commit(message='rev 1-1', rev_id='1-1')
216
dir_2 = br1.bzrdir.sprout('br2')
217
br2 = dir_2.open_branch()
218
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')
219
158
self.build_tree_contents([('br1/file', 'original from 1\n')])
220
wt1.commit(message='rev 1-2', rev_id='1-2')
159
br1.working_tree().commit(message='rev 1-2', rev_id='1-2')
221
160
self.build_tree_contents([('br1/file', 'agreement\n')])
222
wt1.commit(message='rev 1-3', rev_id='1-3')
161
br1.working_tree().commit(message='rev 1-3', rev_id='1-3')
223
162
self.build_tree_contents([('br2/file', 'contents in 2\n')])
224
wt2.commit(message='rev 2-1', rev_id='2-1')
163
br2.working_tree().commit(message='rev 2-1', rev_id='2-1')
225
164
self.build_tree_contents([('br2/file', 'agreement\n')])
226
wt2.commit(message='rev 2-2', rev_id='2-2')
165
br2.working_tree().commit(message='rev 2-2', rev_id='2-2')
228
167
def test_merge_fetches_file_history(self):
229
168
"""Merge brings across file histories"""
234
173
('1-3', 'agreement\n'),
235
174
('2-1', 'contents in 2\n'),
236
175
('2-2', 'agreement\n')]:
237
self.assertEqualDiff(
238
br2.repository.revision_tree(
239
rev_id).get_file_text('this-file-id'), text)
176
self.assertEqualDiff(br2.revision_tree(rev_id).get_file_text('this-file-id'),
242
182
class TestHttpFetch(TestCaseWithWebserver):
243
# FIXME RBC 20060124 this really isn't web specific, perhaps an
244
# instrumented readonly transport? Can we do an instrumented
245
# adapter and use self.get_readonly_url ?
185
super(TestHttpFetch, self).setUp()
247
188
def test_fetch(self):
248
189
#highest indices a: 5, b: 7
249
190
br_a, br_b = make_branches(self)
250
br_rem_a = Branch.open(self.get_readonly_url('branch1'))
191
br_rem_a = Branch.open(self.get_remote_url(br_a._transport.base))
251
192
fetch_steps(self, br_rem_a, br_b, br_a)
253
def _count_log_matches(self, target, logs):
254
"""Count the number of times the target file pattern was fetched in an http log"""
255
get_succeeds_re = re.compile(
256
'.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
257
( target, bzrlib.__version__))
260
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])
264
201
def test_weaves_are_retrieved_once(self):
265
202
self.build_tree(("source/", "source/file", "target/"))
266
wt = self.make_branch_and_tree('source')
268
wt.add(["file"], ["id"])
269
wt.commit("added file")
203
branch = Branch.initialize("source")
204
branch.working_tree().add(["file"], ["id"])
205
branch.working_tree().commit("added file")
270
206
print >>open("source/file", 'w'), "blah"
271
wt.commit("changed file")
272
target = BzrDir.create_branch_and_repo("target/")
273
source = Branch.open(self.get_readonly_url("source/"))
274
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, []))
275
211
# this is the path to the literal file. As format changes
276
212
# occur it needs to be updated. FIXME: ask the store for the
278
self.log("web server logs are:")
279
http_logs = self.get_readonly_server().logs
280
self.log('\n'.join(http_logs))
281
# unfortunately this log entry is branch format specific. We could
282
# factor out the 'what files does this format use' to a method on the
283
# repository, which would let us to this generically. RBC 20060419
284
self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
285
self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
286
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)]))
287
221
# this r-h check test will prevent regressions, but it currently already
288
222
# passes, before the patch to cache-rh is applied :[
289
self.assertTrue(1 >= self._count_log_matches('revision-history',
291
self.assertTrue(1 >= self._count_log_matches('last-revision',
293
# FIXME naughty poking in there.
294
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)]))
295
228
# check there is nothing more to fetch
296
source = Branch.open(self.get_readonly_url("source/"))
297
self.assertEqual(target.fetch(source), (0, []))
298
# should make just two requests
299
http_logs = self.get_readonly_server().logs
300
self.log("web server logs are:")
301
self.log('\n'.join(http_logs))
302
self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
303
self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
304
self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
305
self.assertTrue(1 >= self._count_log_matches('revision-history',
307
self.assertTrue(1 >= self._count_log_matches('last-revision',
309
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))