1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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
20
from bzrlib import bzrdir, repository
21
from bzrlib.branch import Branch
22
from bzrlib.bzrdir import BzrDir
23
from bzrlib.builtins import merge
25
from bzrlib.tests import TestCaseWithTransport
26
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
27
from bzrlib.tests.test_revision import make_branches
28
from bzrlib.trace import mutter
29
from bzrlib.upgrade import Convert
30
from bzrlib.workingtree import WorkingTree
33
def has_revision(branch, revision_id):
34
return branch.repository.has_revision(revision_id)
36
def fetch_steps(self, br_a, br_b, writable_a):
37
"""A foreign test method for testing fetch locally and remotely."""
39
# TODO RBC 20060201 make this a repository test.
40
repo_b = br_b.repository
41
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
42
self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
43
self.assertEquals(len(br_b.revision_history()), 7)
44
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
45
# branch.fetch is not supposed to alter the revision history
46
self.assertEquals(len(br_b.revision_history()), 7)
47
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
49
# fetching the next revision up in sample data copies one revision
50
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
51
self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
52
self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
53
self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
55
# When a non-branch ancestor is missing, it should be unlisted...
56
# as its not reference from the inventory weave.
57
br_b4 = self.make_branch('br_4')
58
count, failures = br_b4.fetch(br_b)
59
self.assertEqual(count, 7)
60
self.assertEqual(failures, [])
62
self.assertEqual(writable_a.fetch(br_b)[0], 1)
63
self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
64
self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
66
br_b2 = self.make_branch('br_b2')
67
self.assertEquals(br_b2.fetch(br_b)[0], 7)
68
self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
69
self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
70
self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
72
br_a2 = self.make_branch('br_a2')
73
self.assertEquals(br_a2.fetch(br_a)[0], 9)
74
self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
75
self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
76
self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
78
br_a3 = self.make_branch('br_a3')
79
# pulling a branch with no revisions grabs nothing, regardless of
80
# whats in the inventory.
81
self.assertEquals(br_a3.fetch(br_a2)[0], 0)
82
for revno in range(4):
84
br_a3.repository.has_revision(br_a.revision_history()[revno]))
85
self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
86
# pull the 3 revisions introduced by a@u-0-3
87
fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
88
self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
89
# InstallFailed should be raised if the branch is missing the revision
91
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
92
# InstallFailed should be raised if the branch is missing a revision
93
# from its own revision history
94
br_a2.append_revision('a-b-c')
95
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
97
# TODO: jam 20051218 Branch should no longer allow append_revision for revisions
98
# which don't exist. So this test needs to be rewritten
99
# RBC 20060403 the way to do this is to uncommit the revision from the
100
# repository after the commit
102
#TODO: test that fetch correctly does reweaving when needed. RBC 20051008
103
# Note that this means - updating the weave when ghosts are filled in to
104
# add the right parents.
107
class TestFetch(TestCaseWithTransport):
109
def test_fetch(self):
110
#highest indices a: 5, b: 7
111
br_a, br_b = make_branches(self)
112
fetch_steps(self, br_a, br_b, br_a)
114
def test_fetch_self(self):
115
wt = self.make_branch_and_tree('br')
116
self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
118
def test_fetch_root_knit(self):
119
"""Ensure that knit2.fetch() updates the root knit
121
This tests the case where the root has a new revision, but there are no
122
corresponding filename, parent, contents or other changes.
124
knit1_format = bzrdir.BzrDirMetaFormat1()
125
knit1_format.repository_format = repository.RepositoryFormatKnit1()
126
knit2_format = bzrdir.BzrDirMetaFormat1()
127
knit2_format.repository_format = repository.RepositoryFormatKnit2()
128
# we start with a knit1 repository because that causes the
129
# root revision to change for each commit, even though the content,
130
# parent, name, and other attributes are unchanged.
131
tree = self.make_branch_and_tree('tree', knit1_format)
132
tree.set_root_id('tree-root')
133
tree.commit('rev1', rev_id='rev1')
134
tree.commit('rev2', rev_id='rev2')
136
# Now we convert it to a knit2 repository so that it has a root knit
137
Convert(tree.basedir, knit2_format)
138
tree = WorkingTree.open(tree.basedir)
139
branch = self.make_branch('branch', format=knit2_format)
140
branch.pull(tree.branch, stop_revision='rev1')
141
repo = branch.repository
142
root_knit = repo.weave_store.get_weave('tree-root',
143
repo.get_transaction())
144
# Make sure fetch retrieved only what we requested
145
self.assertTrue('rev1' in root_knit)
146
self.assertTrue('rev2' not in root_knit)
147
branch.pull(tree.branch)
148
root_knit = repo.weave_store.get_weave('tree-root',
149
repo.get_transaction())
150
# Make sure that the next revision in the root knit was retrieved,
151
# even though the text, name, parent_id, etc., were unchanged.
152
self.assertTrue('rev2' in root_knit)
155
class TestMergeFetch(TestCaseWithTransport):
157
def test_merge_fetches_unrelated(self):
158
"""Merge brings across history from unrelated source"""
159
wt1 = self.make_branch_and_tree('br1')
161
wt1.commit(message='rev 1-1', rev_id='1-1')
162
wt1.commit(message='rev 1-2', rev_id='1-2')
163
wt2 = self.make_branch_and_tree('br2')
165
wt2.commit(message='rev 2-1', rev_id='2-1')
166
merge(other_revision=['br1', -1], base_revision=['br1', 0],
168
self._check_revs_present(br2)
170
def test_merge_fetches(self):
171
"""Merge brings across history from source"""
172
wt1 = self.make_branch_and_tree('br1')
174
wt1.commit(message='rev 1-1', rev_id='1-1')
175
dir_2 = br1.bzrdir.sprout('br2')
176
br2 = dir_2.open_branch()
177
wt1.commit(message='rev 1-2', rev_id='1-2')
178
dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
179
merge(other_revision=['br1', -1], base_revision=[None, None],
181
self._check_revs_present(br2)
183
def _check_revs_present(self, br2):
184
for rev_id in '1-1', '1-2', '2-1':
185
self.assertTrue(br2.repository.has_revision(rev_id))
186
rev = br2.repository.get_revision(rev_id)
187
self.assertEqual(rev.revision_id, rev_id)
188
self.assertTrue(br2.repository.get_inventory(rev_id))
191
class TestMergeFileHistory(TestCaseWithTransport):
194
super(TestMergeFileHistory, self).setUp()
195
wt1 = self.make_branch_and_tree('br1')
197
self.build_tree_contents([('br1/file', 'original contents\n')])
198
wt1.add('file', 'this-file-id')
199
wt1.commit(message='rev 1-1', rev_id='1-1')
200
dir_2 = br1.bzrdir.sprout('br2')
201
br2 = dir_2.open_branch()
202
wt2 = dir_2.open_workingtree()
203
self.build_tree_contents([('br1/file', 'original from 1\n')])
204
wt1.commit(message='rev 1-2', rev_id='1-2')
205
self.build_tree_contents([('br1/file', 'agreement\n')])
206
wt1.commit(message='rev 1-3', rev_id='1-3')
207
self.build_tree_contents([('br2/file', 'contents in 2\n')])
208
wt2.commit(message='rev 2-1', rev_id='2-1')
209
self.build_tree_contents([('br2/file', 'agreement\n')])
210
wt2.commit(message='rev 2-2', rev_id='2-2')
212
def test_merge_fetches_file_history(self):
213
"""Merge brings across file histories"""
214
br2 = Branch.open('br2')
215
merge(other_revision=['br1', -1], base_revision=[None, None],
217
for rev_id, text in [('1-2', 'original from 1\n'),
218
('1-3', 'agreement\n'),
219
('2-1', 'contents in 2\n'),
220
('2-2', 'agreement\n')]:
221
self.assertEqualDiff(
222
br2.repository.revision_tree(
223
rev_id).get_file_text('this-file-id'), text)
226
class TestHttpFetch(TestCaseWithWebserver):
227
# FIXME RBC 20060124 this really isn't web specific, perhaps an
228
# instrumented readonly transport? Can we do an instrumented
229
# adapter and use self.get_readonly_url ?
231
def test_fetch(self):
232
#highest indices a: 5, b: 7
233
br_a, br_b = make_branches(self)
234
br_rem_a = Branch.open(self.get_readonly_url('branch1'))
235
fetch_steps(self, br_rem_a, br_b, br_a)
237
def _count_log_matches(self, target, logs):
238
"""Count the number of times the target file pattern was fetched in an http log"""
239
log_pattern = '%s HTTP/1.1" 200 - "-" "bzr/%s' % \
240
(target, bzrlib.__version__)
243
# TODO: perhaps use a regexp instead so we can match more
245
if line.find(log_pattern) > -1:
249
def test_weaves_are_retrieved_once(self):
250
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")
255
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, []))
260
log_pattern = '%%s HTTP/1.1" 200 - "-" "bzr/%s' % bzrlib.__version__
261
# this is the path to the literal file. As format changes
262
# occur it needs to be updated. FIXME: ask the store for the
264
self.log("web server logs are:")
265
http_logs = self.get_readonly_server().logs
266
self.log('\n'.join(http_logs))
267
# unfortunately this log entry is branch format specific. We could
268
# factor out the 'what files does this format use' to a method on the
269
# repository, which would let us to this generically. RBC 20060419
270
self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
271
self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
272
self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
273
# this r-h check test will prevent regressions, but it currently already
274
# passes, before the patch to cache-rh is applied :[
275
self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
276
# FIXME naughty poking in there.
277
self.get_readonly_server().logs = []
278
# check there is nothing more to fetch
279
source = Branch.open(self.get_readonly_url("source/"))
280
self.assertEqual(target.fetch(source), (0, []))
281
# should make just two requests
282
http_logs = self.get_readonly_server().logs
283
self.log("web server logs are:")
284
self.log('\n'.join(http_logs))
285
self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
286
self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
287
self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
288
self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
289
self.assertEqual(4, len(http_logs))