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
21
from bzrlib import bzrdir, repository
22
from bzrlib.branch import Branch
23
from bzrlib.bzrdir import BzrDir
24
from bzrlib.builtins import merge
26
from bzrlib.repofmt import knitrepo
27
from bzrlib.tests import TestCaseWithTransport
28
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
29
from bzrlib.tests.test_revision import make_branches
30
from bzrlib.trace import mutter
31
from bzrlib.upgrade import Convert
32
from bzrlib.workingtree import WorkingTree
35
def has_revision(branch, revision_id):
36
return branch.repository.has_revision(revision_id)
38
def fetch_steps(self, br_a, br_b, writable_a):
39
"""A foreign test method for testing fetch locally and remotely."""
41
# TODO RBC 20060201 make this a repository test.
42
repo_b = br_b.repository
43
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
44
self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
45
self.assertEquals(len(br_b.revision_history()), 7)
46
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
47
# branch.fetch is not supposed to alter the revision history
48
self.assertEquals(len(br_b.revision_history()), 7)
49
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
51
# fetching the next revision up in sample data copies one revision
52
self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
53
self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
54
self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
55
self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
57
# When a non-branch ancestor is missing, it should be unlisted...
58
# as its not reference from the inventory weave.
59
br_b4 = self.make_branch('br_4')
60
count, failures = br_b4.fetch(br_b)
61
self.assertEqual(count, 7)
62
self.assertEqual(failures, [])
64
self.assertEqual(writable_a.fetch(br_b)[0], 1)
65
self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
66
self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
68
br_b2 = self.make_branch('br_b2')
69
self.assertEquals(br_b2.fetch(br_b)[0], 7)
70
self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
71
self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
72
self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
74
br_a2 = self.make_branch('br_a2')
75
self.assertEquals(br_a2.fetch(br_a)[0], 9)
76
self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
77
self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
78
self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
80
br_a3 = self.make_branch('br_a3')
81
# pulling a branch with no revisions grabs nothing, regardless of
82
# whats in the inventory.
83
self.assertEquals(br_a3.fetch(br_a2)[0], 0)
84
for revno in range(4):
86
br_a3.repository.has_revision(br_a.revision_history()[revno]))
87
self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
88
# pull the 3 revisions introduced by a@u-0-3
89
fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
90
self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
91
# InstallFailed should be raised if the branch is missing the revision
93
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
94
# InstallFailed should be raised if the branch is missing a revision
95
# from its own revision history
96
br_a2.append_revision('a-b-c')
97
self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
99
# TODO: jam 20051218 Branch should no longer allow append_revision for revisions
100
# which don't exist. So this test needs to be rewritten
101
# RBC 20060403 the way to do this is to uncommit the revision from the
102
# repository after the commit
104
#TODO: test that fetch correctly does reweaving when needed. RBC 20051008
105
# Note that this means - updating the weave when ghosts are filled in to
106
# add the right parents.
109
class TestFetch(TestCaseWithTransport):
111
def test_fetch(self):
112
#highest indices a: 5, b: 7
113
br_a, br_b = make_branches(self)
114
fetch_steps(self, br_a, br_b, br_a)
116
def test_fetch_self(self):
117
wt = self.make_branch_and_tree('br')
118
self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
120
def test_fetch_root_knit(self):
121
"""Ensure that knit2.fetch() updates the root knit
123
This tests the case where the root has a new revision, but there are no
124
corresponding filename, parent, contents or other changes.
126
knit1_format = bzrdir.BzrDirMetaFormat1()
127
knit1_format.repository_format = repository.RepositoryFormatKnit1()
128
knit2_format = bzrdir.BzrDirMetaFormat1()
129
knit2_format.repository_format = knitrepo.RepositoryFormatKnit2()
130
# we start with a knit1 repository because that causes the
131
# root revision to change for each commit, even though the content,
132
# parent, name, and other attributes are unchanged.
133
tree = self.make_branch_and_tree('tree', knit1_format)
134
tree.set_root_id('tree-root')
135
tree.commit('rev1', rev_id='rev1')
136
tree.commit('rev2', rev_id='rev2')
138
# Now we convert it to a knit2 repository so that it has a root knit
139
Convert(tree.basedir, knit2_format)
140
tree = WorkingTree.open(tree.basedir)
141
branch = self.make_branch('branch', format=knit2_format)
142
branch.pull(tree.branch, stop_revision='rev1')
143
repo = branch.repository
144
root_knit = repo.weave_store.get_weave('tree-root',
145
repo.get_transaction())
146
# Make sure fetch retrieved only what we requested
147
self.assertTrue('rev1' in root_knit)
148
self.assertTrue('rev2' not in root_knit)
149
branch.pull(tree.branch)
150
root_knit = repo.weave_store.get_weave('tree-root',
151
repo.get_transaction())
152
# Make sure that the next revision in the root knit was retrieved,
153
# even though the text, name, parent_id, etc., were unchanged.
154
self.assertTrue('rev2' in root_knit)
157
class TestMergeFetch(TestCaseWithTransport):
159
def test_merge_fetches_unrelated(self):
160
"""Merge brings across history from unrelated source"""
161
wt1 = self.make_branch_and_tree('br1')
163
wt1.commit(message='rev 1-1', rev_id='1-1')
164
wt1.commit(message='rev 1-2', rev_id='1-2')
165
wt2 = self.make_branch_and_tree('br2')
167
wt2.commit(message='rev 2-1', rev_id='2-1')
168
merge(other_revision=['br1', -1], base_revision=['br1', 0],
170
self._check_revs_present(br2)
172
def test_merge_fetches(self):
173
"""Merge brings across history from source"""
174
wt1 = self.make_branch_and_tree('br1')
176
wt1.commit(message='rev 1-1', rev_id='1-1')
177
dir_2 = br1.bzrdir.sprout('br2')
178
br2 = dir_2.open_branch()
179
wt1.commit(message='rev 1-2', rev_id='1-2')
180
dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
181
merge(other_revision=['br1', -1], base_revision=[None, None],
183
self._check_revs_present(br2)
185
def _check_revs_present(self, br2):
186
for rev_id in '1-1', '1-2', '2-1':
187
self.assertTrue(br2.repository.has_revision(rev_id))
188
rev = br2.repository.get_revision(rev_id)
189
self.assertEqual(rev.revision_id, rev_id)
190
self.assertTrue(br2.repository.get_inventory(rev_id))
193
class TestMergeFileHistory(TestCaseWithTransport):
196
super(TestMergeFileHistory, self).setUp()
197
wt1 = self.make_branch_and_tree('br1')
199
self.build_tree_contents([('br1/file', 'original contents\n')])
200
wt1.add('file', 'this-file-id')
201
wt1.commit(message='rev 1-1', rev_id='1-1')
202
dir_2 = br1.bzrdir.sprout('br2')
203
br2 = dir_2.open_branch()
204
wt2 = dir_2.open_workingtree()
205
self.build_tree_contents([('br1/file', 'original from 1\n')])
206
wt1.commit(message='rev 1-2', rev_id='1-2')
207
self.build_tree_contents([('br1/file', 'agreement\n')])
208
wt1.commit(message='rev 1-3', rev_id='1-3')
209
self.build_tree_contents([('br2/file', 'contents in 2\n')])
210
wt2.commit(message='rev 2-1', rev_id='2-1')
211
self.build_tree_contents([('br2/file', 'agreement\n')])
212
wt2.commit(message='rev 2-2', rev_id='2-2')
214
def test_merge_fetches_file_history(self):
215
"""Merge brings across file histories"""
216
br2 = Branch.open('br2')
217
merge(other_revision=['br1', -1], base_revision=[None, None],
219
for rev_id, text in [('1-2', 'original from 1\n'),
220
('1-3', 'agreement\n'),
221
('2-1', 'contents in 2\n'),
222
('2-2', 'agreement\n')]:
223
self.assertEqualDiff(
224
br2.repository.revision_tree(
225
rev_id).get_file_text('this-file-id'), text)
228
class TestHttpFetch(TestCaseWithWebserver):
229
# FIXME RBC 20060124 this really isn't web specific, perhaps an
230
# instrumented readonly transport? Can we do an instrumented
231
# adapter and use self.get_readonly_url ?
233
def test_fetch(self):
234
#highest indices a: 5, b: 7
235
br_a, br_b = make_branches(self)
236
br_rem_a = Branch.open(self.get_readonly_url('branch1'))
237
fetch_steps(self, br_rem_a, br_b, br_a)
239
def _count_log_matches(self, target, logs):
240
"""Count the number of times the target file pattern was fetched in an http log"""
241
get_succeeds_re = re.compile(
242
'.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
243
( target, bzrlib.__version__))
246
if get_succeeds_re.match(line):
250
def test_weaves_are_retrieved_once(self):
251
self.build_tree(("source/", "source/file", "target/"))
252
wt = self.make_branch_and_tree('source')
254
wt.add(["file"], ["id"])
255
wt.commit("added file")
256
print >>open("source/file", 'w'), "blah"
257
wt.commit("changed file")
258
target = BzrDir.create_branch_and_repo("target/")
259
source = Branch.open(self.get_readonly_url("source/"))
260
self.assertEqual(target.fetch(source), (2, []))
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))