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: ADHB 20070116 Perhaps set_last_revision shouldn't accept
100
# revisions which are not present? In that case, this test
103
# RBC 20060403 the way to do this is to uncommit the revision from
104
# the repository after the commit
106
#TODO: test that fetch correctly does reweaving when needed. RBC 20051008
107
# Note that this means - updating the weave when ghosts are filled in to
108
# add the right parents.
111
class TestFetch(TestCaseWithTransport):
113
def test_fetch(self):
114
#highest indices a: 5, b: 7
115
br_a, br_b = make_branches(self)
116
fetch_steps(self, br_a, br_b, br_a)
118
def test_fetch_self(self):
119
wt = self.make_branch_and_tree('br')
120
self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
122
def test_fetch_root_knit(self):
123
"""Ensure that knit2.fetch() updates the root knit
125
This tests the case where the root has a new revision, but there are no
126
corresponding filename, parent, contents or other changes.
128
knit1_format = bzrdir.BzrDirMetaFormat1()
129
knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
130
knit2_format = bzrdir.BzrDirMetaFormat1()
131
knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
132
# we start with a knit1 repository because that causes the
133
# root revision to change for each commit, even though the content,
134
# parent, name, and other attributes are unchanged.
135
tree = self.make_branch_and_tree('tree', knit1_format)
136
tree.set_root_id('tree-root')
137
tree.commit('rev1', rev_id='rev1')
138
tree.commit('rev2', rev_id='rev2')
140
# Now we convert it to a knit2 repository so that it has a root knit
141
Convert(tree.basedir, knit2_format)
142
tree = WorkingTree.open(tree.basedir)
143
branch = self.make_branch('branch', format=knit2_format)
144
branch.pull(tree.branch, stop_revision='rev1')
145
repo = branch.repository
146
root_knit = repo.weave_store.get_weave('tree-root',
147
repo.get_transaction())
148
# Make sure fetch retrieved only what we requested
149
self.assertTrue('rev1' in root_knit)
150
self.assertTrue('rev2' not in root_knit)
151
branch.pull(tree.branch)
152
root_knit = repo.weave_store.get_weave('tree-root',
153
repo.get_transaction())
154
# Make sure that the next revision in the root knit was retrieved,
155
# even though the text, name, parent_id, etc., were unchanged.
156
self.assertTrue('rev2' in root_knit)
159
class TestMergeFetch(TestCaseWithTransport):
161
def test_merge_fetches_unrelated(self):
162
"""Merge brings across history from unrelated source"""
163
wt1 = self.make_branch_and_tree('br1')
165
wt1.commit(message='rev 1-1', rev_id='1-1')
166
wt1.commit(message='rev 1-2', rev_id='1-2')
167
wt2 = self.make_branch_and_tree('br2')
169
wt2.commit(message='rev 2-1', rev_id='2-1')
170
merge(other_revision=['br1', -1], base_revision=['br1', 0],
172
self._check_revs_present(br2)
174
def test_merge_fetches(self):
175
"""Merge brings across history from source"""
176
wt1 = self.make_branch_and_tree('br1')
178
wt1.commit(message='rev 1-1', rev_id='1-1')
179
dir_2 = br1.bzrdir.sprout('br2')
180
br2 = dir_2.open_branch()
181
wt1.commit(message='rev 1-2', rev_id='1-2')
182
dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
183
merge(other_revision=['br1', -1], base_revision=[None, None],
185
self._check_revs_present(br2)
187
def _check_revs_present(self, br2):
188
for rev_id in '1-1', '1-2', '2-1':
189
self.assertTrue(br2.repository.has_revision(rev_id))
190
rev = br2.repository.get_revision(rev_id)
191
self.assertEqual(rev.revision_id, rev_id)
192
self.assertTrue(br2.repository.get_inventory(rev_id))
195
class TestMergeFileHistory(TestCaseWithTransport):
198
super(TestMergeFileHistory, self).setUp()
199
wt1 = self.make_branch_and_tree('br1')
201
self.build_tree_contents([('br1/file', 'original contents\n')])
202
wt1.add('file', 'this-file-id')
203
wt1.commit(message='rev 1-1', rev_id='1-1')
204
dir_2 = br1.bzrdir.sprout('br2')
205
br2 = dir_2.open_branch()
206
wt2 = dir_2.open_workingtree()
207
self.build_tree_contents([('br1/file', 'original from 1\n')])
208
wt1.commit(message='rev 1-2', rev_id='1-2')
209
self.build_tree_contents([('br1/file', 'agreement\n')])
210
wt1.commit(message='rev 1-3', rev_id='1-3')
211
self.build_tree_contents([('br2/file', 'contents in 2\n')])
212
wt2.commit(message='rev 2-1', rev_id='2-1')
213
self.build_tree_contents([('br2/file', 'agreement\n')])
214
wt2.commit(message='rev 2-2', rev_id='2-2')
216
def test_merge_fetches_file_history(self):
217
"""Merge brings across file histories"""
218
br2 = Branch.open('br2')
219
merge(other_revision=['br1', -1], base_revision=[None, None],
221
for rev_id, text in [('1-2', 'original from 1\n'),
222
('1-3', 'agreement\n'),
223
('2-1', 'contents in 2\n'),
224
('2-2', 'agreement\n')]:
225
self.assertEqualDiff(
226
br2.repository.revision_tree(
227
rev_id).get_file_text('this-file-id'), text)
230
class TestHttpFetch(TestCaseWithWebserver):
231
# FIXME RBC 20060124 this really isn't web specific, perhaps an
232
# instrumented readonly transport? Can we do an instrumented
233
# adapter and use self.get_readonly_url ?
235
def test_fetch(self):
236
#highest indices a: 5, b: 7
237
br_a, br_b = make_branches(self)
238
br_rem_a = Branch.open(self.get_readonly_url('branch1'))
239
fetch_steps(self, br_rem_a, br_b, br_a)
241
def _count_log_matches(self, target, logs):
242
"""Count the number of times the target file pattern was fetched in an http log"""
243
get_succeeds_re = re.compile(
244
'.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
245
( target, bzrlib.__version__))
248
if get_succeeds_re.match(line):
252
def test_weaves_are_retrieved_once(self):
253
self.build_tree(("source/", "source/file", "target/"))
254
wt = self.make_branch_and_tree('source')
256
wt.add(["file"], ["id"])
257
wt.commit("added file")
258
print >>open("source/file", 'w'), "blah"
259
wt.commit("changed file")
260
target = BzrDir.create_branch_and_repo("target/")
261
source = Branch.open(self.get_readonly_url("source/"))
262
self.assertEqual(target.fetch(source), (2, []))
263
# this is the path to the literal file. As format changes
264
# occur it needs to be updated. FIXME: ask the store for the
266
self.log("web server logs are:")
267
http_logs = self.get_readonly_server().logs
268
self.log('\n'.join(http_logs))
269
# unfortunately this log entry is branch format specific. We could
270
# factor out the 'what files does this format use' to a method on the
271
# repository, which would let us to this generically. RBC 20060419
272
self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
273
self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
274
self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
275
# this r-h check test will prevent regressions, but it currently already
276
# passes, before the patch to cache-rh is applied :[
277
self.assertTrue(1 >= self._count_log_matches('revision-history',
279
self.assertTrue(1 >= self._count_log_matches('last-revision',
281
# FIXME naughty poking in there.
282
self.get_readonly_server().logs = []
283
# check there is nothing more to fetch
284
source = Branch.open(self.get_readonly_url("source/"))
285
self.assertEqual(target.fetch(source), (0, []))
286
# should make just two requests
287
http_logs = self.get_readonly_server().logs
288
self.log("web server logs are:")
289
self.log('\n'.join(http_logs))
290
self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
291
self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
292
self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
293
self.assertTrue(1 >= self._count_log_matches('revision-history',
295
self.assertTrue(1 >= self._count_log_matches('last-revision',
297
self.assertEqual(4, len(http_logs))