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
26
from bzrlib.branch import Branch
27
from bzrlib.bzrdir import BzrDir
28
from bzrlib.builtins import merge
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
34
from bzrlib.trace import mutter
35
from bzrlib.upgrade import Convert
36
from bzrlib.workingtree import WorkingTree
39
def has_revision(branch, revision_id):
40
return branch.repository.has_revision(revision_id)
42
def fetch_steps(self, br_a, br_b, writable_a):
43
"""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]))
61
# When a non-branch ancestor is missing, it should be unlisted...
62
# as its not reference from the inventory weave.
63
br_b4 = self.make_branch('br_4')
64
count, failures = br_b4.fetch(br_b)
65
self.assertEqual(count, 7)
66
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]))
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)
88
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)
95
# InstallFailed should be raised if the branch is missing the revision
97
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
98
# InstallFailed should be raised if the branch is missing a revision
99
# from its own revision history
100
br_a2.append_revision('a-b-c')
101
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
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
#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):
117
def test_fetch(self):
118
#highest indices a: 5, b: 7
119
br_a, br_b = make_branches(self)
120
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):
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 ?
247
def test_fetch(self):
248
#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'))
251
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):
264
def test_weaves_are_retrieved_once(self):
265
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")
270
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, []))
275
# this is the path to the literal file. As format changes
276
# 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))
287
# this r-h check test will prevent regressions, but it currently already
288
# 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 = []
295
# 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))