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
from bzrlib.tests.test_revision import make_branches
21
from bzrlib.selftest.testrevision 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
26
from bzrlib.selftest import TestCaseInTempDir
27
from bzrlib.selftest.HTTPTestUtil import TestCaseWithWebserver
39
30
def has_revision(branch, revision_id):
40
return branch.repository.has_revision(revision_id)
32
branch.get_revision_xml_file(revision_id)
34
except bzrlib.errors.NoSuchRevision:
42
37
def fetch_steps(self, br_a, br_b, writable_a):
43
38
"""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]))
58
self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
59
self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
41
return Branch.initialize(name)
43
assert not has_revision(br_b, br_a.revision_history()[3])
44
assert has_revision(br_b, br_a.revision_history()[2])
45
assert len(br_b.revision_history()) == 7
46
assert greedy_fetch(br_b, br_a, br_a.revision_history()[2])[0] == 0
48
# greedy_fetch is not supposed to alter the revision history
49
assert len(br_b.revision_history()) == 7
50
assert not has_revision(br_b, br_a.revision_history()[3])
52
assert len(br_b.revision_history()) == 7
53
assert greedy_fetch(br_b, br_a, br_a.revision_history()[3])[0] == 1
54
assert has_revision(br_b, br_a.revision_history()[3])
55
assert not has_revision(br_a, br_b.revision_history()[6])
56
assert has_revision(br_a, br_b.revision_history()[5])
61
58
# When a non-branch ancestor is missing, it should be unlisted...
62
59
# as its not reference from the inventory weave.
63
br_b4 = self.make_branch('br_4')
64
count, failures = br_b4.fetch(br_b)
60
br_b4 = new_branch('br_4')
61
count, failures = greedy_fetch(br_b4, br_b)
65
62
self.assertEqual(count, 7)
66
63
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]))
65
self.assertEqual(greedy_fetch(writable_a, br_b)[0], 1)
66
assert has_revision(br_a, br_b.revision_history()[3])
67
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]))
76
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]))
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)
69
br_b2 = new_branch('br_b2')
70
assert greedy_fetch(br_b2, br_b)[0] == 7
71
assert has_revision(br_b2, br_b.revision_history()[4])
72
assert has_revision(br_b2, br_a.revision_history()[2])
73
assert not has_revision(br_b2, br_a.revision_history()[3])
75
br_a2 = new_branch('br_a2')
76
assert greedy_fetch(br_a2, br_a)[0] == 9
77
assert has_revision(br_a2, br_b.revision_history()[4])
78
assert has_revision(br_a2, br_a.revision_history()[3])
79
assert has_revision(br_a2, br_a.revision_history()[2])
81
br_a3 = new_branch('br_a3')
82
assert greedy_fetch(br_a3, br_a2)[0] == 0
88
83
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]
94
self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
84
assert not has_revision(br_a3, br_a.revision_history()[revno])
85
self.assertEqual(greedy_fetch(br_a3, br_a2, br_a.revision_history()[2])[0], 3)
86
fetched = greedy_fetch(br_a3, br_a2, br_a.revision_history()[3])[0]
87
assert fetched == 3, "fetched %d instead of 3" % fetched
95
88
# InstallFailed should be raised if the branch is missing the revision
96
89
# that was requested.
97
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
90
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
98
92
# InstallFailed should be raised if the branch is missing a revision
99
93
# from its own revision history
100
94
br_a2.append_revision('a-b-c')
101
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
95
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
99
#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):
101
class TestFetch(TestCaseInTempDir):
117
103
def test_fetch(self):
118
104
#highest indices a: 5, b: 7
119
br_a, br_b = make_branches(self)
105
br_a, br_b = make_branches()
120
106
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):
173
def test_merge_fetches_unrelated(self):
174
"""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')
182
merge(other_revision=['br1', -1], base_revision=['br1', 0],
184
self._check_revs_present(br2)
186
def test_merge_fetches(self):
187
"""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')
195
merge(other_revision=['br1', -1], base_revision=[None, None],
197
self._check_revs_present(br2)
199
def _check_revs_present(self, br2):
200
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)
203
self.assertEqual(rev.revision_id, rev_id)
204
self.assertTrue(br2.repository.get_inventory(rev_id))
207
class TestMergeFileHistory(TestCaseWithTransport):
109
class TestHttpFetch(TestCaseWithWebserver):
210
super(TestMergeFileHistory, self).setUp()
211
wt1 = self.make_branch_and_tree('br1')
213
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()
219
self.build_tree_contents([('br1/file', 'original from 1\n')])
220
wt1.commit(message='rev 1-2', rev_id='1-2')
221
self.build_tree_contents([('br1/file', 'agreement\n')])
222
wt1.commit(message='rev 1-3', rev_id='1-3')
223
self.build_tree_contents([('br2/file', 'contents in 2\n')])
224
wt2.commit(message='rev 2-1', rev_id='2-1')
225
self.build_tree_contents([('br2/file', 'agreement\n')])
226
wt2.commit(message='rev 2-2', rev_id='2-2')
228
def test_merge_fetches_file_history(self):
229
"""Merge brings across file histories"""
230
br2 = Branch.open('br2')
231
merge(other_revision=['br1', -1], base_revision=[None, None],
233
for rev_id, text in [('1-2', 'original from 1\n'),
234
('1-3', 'agreement\n'),
235
('2-1', 'contents in 2\n'),
236
('2-2', 'agreement\n')]:
237
self.assertEqualDiff(
238
br2.repository.revision_tree(
239
rev_id).get_file_text('this-file-id'), text)
242
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 ?
112
super(TestHttpFetch, self).setUp()
247
115
def test_fetch(self):
248
116
#highest indices a: 5, b: 7
249
br_a, br_b = make_branches(self)
250
br_rem_a = Branch.open(self.get_readonly_url('branch1'))
117
br_a, br_b = make_branches()
118
br_rem_a = Branch.open(self.get_remote_url(br_a._transport.base))
251
119
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):
121
def log(self, *args):
122
"""Capture web server log messages for introspection."""
123
super(TestHttpFetch, self).log(*args)
124
if args[0].startswith("webserver"):
125
self.weblogs.append(args[0])
264
127
def test_weaves_are_retrieved_once(self):
265
128
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")
129
branch = Branch.initialize("source")
130
branch.add(["file"], ["id"])
131
branch.commit("added file")
270
132
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, []))
133
branch.commit("changed file")
134
target = Branch.initialize("target/")
135
source = Branch.open(self.get_remote_url("source/"))
136
self.assertEqual(greedy_fetch(target, source), (2, []))
275
137
# this is the path to the literal file. As format changes
276
138
# 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))
140
weave_suffix = 'weaves/ce/id.weave HTTP/1.1" 200 -'
142
len([log for log in self.weblogs if log.endswith(weave_suffix)]))
143
inventory_weave_suffix = 'inventory.weave HTTP/1.1" 200 -'
145
len([log for log in self.weblogs if log.endswith(
146
inventory_weave_suffix)]))
287
147
# this r-h check test will prevent regressions, but it currently already
288
148
# 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 = []
149
revision_history_suffix = 'revision-history HTTP/1.1" 200 -'
151
len([log for log in self.weblogs if log.endswith(
152
revision_history_suffix)]))
295
154
# 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))
155
source = Branch.open(self.get_remote_url("source/"))
156
self.assertEqual(greedy_fetch(target, source), (0, []))
157
self.failUnless(self.weblogs[0].endswith('branch-format HTTP/1.1" 200 -'))
158
self.failUnless(self.weblogs[1].endswith('revision-history HTTP/1.1" 200 -'))
159
self.assertEqual(2, len(self.weblogs))