1
# Copyright (C) 2005, 2007, 2010 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
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from bzrlib.selftest.testrevision import make_branches
18
from bzrlib.trace import mutter
19
from bzrlib.branch import Branch
30
from bzrlib.branch import Branch
31
from bzrlib.bzrdir import BzrDir
32
from bzrlib.repofmt import knitrepo
33
from bzrlib.tests import TestCaseWithTransport
34
from bzrlib.tests.test_revision import make_branches
35
from bzrlib.trace import mutter
36
from bzrlib.upgrade import Convert
37
from bzrlib.workingtree import WorkingTree
39
# These tests are a bit old; please instead add new tests into
40
# per_interrepository/ so they'll run on all relevant
44
def has_revision(branch, revision_id):
45
return branch.repository.has_revision(revision_id)
47
def fetch_steps(self, br_a, br_b, writable_a):
48
"""A foreign test method for testing fetch locally and remotely."""
50
# TODO RBC 20060201 make this a repository test.
51
repo_b = br_b.repository
52
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
53
self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
54
self.assertEquals(len(br_b.revision_history()), 7)
55
br_b.fetch(br_a, br_a.revision_history()[2])
56
# branch.fetch is not supposed to alter the revision history
57
self.assertEquals(len(br_b.revision_history()), 7)
58
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
60
# fetching the next revision up in sample data copies one revision
61
br_b.fetch(br_a, br_a.revision_history()[3])
62
self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
63
self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
64
self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
66
# When a non-branch ancestor is missing, it should be unlisted...
67
# as its not reference from the inventory weave.
68
br_b4 = self.make_branch('br_4')
71
writable_a.fetch(br_b)
72
self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
73
self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
75
br_b2 = self.make_branch('br_b2')
77
self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
78
self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
79
self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
81
br_a2 = self.make_branch('br_a2')
83
self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
84
self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
85
self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
87
br_a3 = self.make_branch('br_a3')
88
# pulling a branch with no revisions grabs nothing, regardless of
89
# whats in the inventory.
91
for revno in range(4):
93
br_a3.repository.has_revision(br_a.revision_history()[revno]))
94
br_a3.fetch(br_a2, br_a.revision_history()[2])
95
# pull the 3 revisions introduced by a@u-0-3
96
br_a3.fetch(br_a2, br_a.revision_history()[3])
97
# NoSuchRevision should be raised if the branch is missing the revision
99
self.assertRaises(errors.NoSuchRevision, br_a3.fetch, br_a2, 'pizza')
101
# TODO: Test trying to fetch from a branch that points to a revision not
102
# actually present in its repository. Not every branch format allows you
103
# to directly point to such revisions, so it's a bit complicated to
104
# construct. One way would be to uncommit and gc the revision, but not
105
# every branch supports that. -- mbp 20070814
107
#TODO: test that fetch correctly does reweaving when needed. RBC 20051008
108
# Note that this means - updating the weave when ghosts are filled in to
109
# add the right parents.
112
class TestFetch(TestCaseWithTransport):
114
def test_fetch(self):
23
from bzrlib.selftest import TestCaseInTempDir
26
class TestFetch(TestCaseInTempDir):
28
from bzrlib.fetch import greedy_fetch, has_revision
32
return Branch.initialize(name)
115
34
#highest indices a: 5, b: 7
116
br_a, br_b = make_branches(self, format='dirstate-tags')
117
fetch_steps(self, br_a, br_b, br_a)
119
def test_fetch_self(self):
120
wt = self.make_branch_and_tree('br')
121
wt.branch.fetch(wt.branch)
123
def test_fetch_root_knit(self):
124
"""Ensure that knit2.fetch() updates the root knit
126
This tests the case where the root has a new revision, but there are no
127
corresponding filename, parent, contents or other changes.
129
knit1_format = bzrdir.BzrDirMetaFormat1()
130
knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
131
knit2_format = bzrdir.BzrDirMetaFormat1()
132
knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
133
# we start with a knit1 repository because that causes the
134
# root revision to change for each commit, even though the content,
135
# parent, name, and other attributes are unchanged.
136
tree = self.make_branch_and_tree('tree', knit1_format)
137
tree.set_root_id('tree-root')
138
tree.commit('rev1', rev_id='rev1')
139
tree.commit('rev2', rev_id='rev2')
141
# Now we convert it to a knit2 repository so that it has a root knit
142
Convert(tree.basedir, knit2_format)
143
tree = WorkingTree.open(tree.basedir)
144
branch = self.make_branch('branch', format=knit2_format)
145
branch.pull(tree.branch, stop_revision='rev1')
146
repo = branch.repository
149
# Make sure fetch retrieved only what we requested
150
self.assertEqual({('tree-root', 'rev1'):()},
151
repo.texts.get_parent_map(
152
[('tree-root', 'rev1'), ('tree-root', 'rev2')]))
155
branch.pull(tree.branch)
156
# Make sure that the next revision in the root knit was retrieved,
157
# even though the text, name, parent_id, etc., were unchanged.
160
# Make sure fetch retrieved only what we requested
161
self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
162
repo.texts.get_parent_map([('tree-root', 'rev2')]))
166
def test_fetch_incompatible(self):
167
knit_tree = self.make_branch_and_tree('knit', format='knit')
168
knit3_tree = self.make_branch_and_tree('knit3',
169
format='dirstate-with-subtree')
170
knit3_tree.commit('blah')
171
e = self.assertRaises(errors.IncompatibleRepositories,
172
knit_tree.branch.fetch, knit3_tree.branch)
173
self.assertContainsRe(str(e),
174
r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
175
r"different rich-root support")
178
class TestMergeFetch(TestCaseWithTransport):
180
def test_merge_fetches_unrelated(self):
181
"""Merge brings across history from unrelated source"""
182
wt1 = self.make_branch_and_tree('br1')
184
wt1.commit(message='rev 1-1', rev_id='1-1')
185
wt1.commit(message='rev 1-2', rev_id='1-2')
186
wt2 = self.make_branch_and_tree('br2')
188
wt2.commit(message='rev 2-1', rev_id='2-1')
189
wt2.merge_from_branch(br1, from_revision='null:')
190
self._check_revs_present(br2)
192
def test_merge_fetches(self):
193
"""Merge brings across history from source"""
194
wt1 = self.make_branch_and_tree('br1')
196
wt1.commit(message='rev 1-1', rev_id='1-1')
197
dir_2 = br1.bzrdir.sprout('br2')
198
br2 = dir_2.open_branch()
199
wt1.commit(message='rev 1-2', rev_id='1-2')
200
wt2 = dir_2.open_workingtree()
201
wt2.commit(message='rev 2-1', rev_id='2-1')
202
wt2.merge_from_branch(br1)
203
self._check_revs_present(br2)
205
def _check_revs_present(self, br2):
206
for rev_id in '1-1', '1-2', '2-1':
207
self.assertTrue(br2.repository.has_revision(rev_id))
208
rev = br2.repository.get_revision(rev_id)
209
self.assertEqual(rev.revision_id, rev_id)
210
self.assertTrue(br2.repository.get_inventory(rev_id))
213
class TestMergeFileHistory(TestCaseWithTransport):
216
super(TestMergeFileHistory, self).setUp()
217
wt1 = self.make_branch_and_tree('br1')
219
self.build_tree_contents([('br1/file', 'original contents\n')])
220
wt1.add('file', 'this-file-id')
221
wt1.commit(message='rev 1-1', rev_id='1-1')
222
dir_2 = br1.bzrdir.sprout('br2')
223
br2 = dir_2.open_branch()
224
wt2 = dir_2.open_workingtree()
225
self.build_tree_contents([('br1/file', 'original from 1\n')])
226
wt1.commit(message='rev 1-2', rev_id='1-2')
227
self.build_tree_contents([('br1/file', 'agreement\n')])
228
wt1.commit(message='rev 1-3', rev_id='1-3')
229
self.build_tree_contents([('br2/file', 'contents in 2\n')])
230
wt2.commit(message='rev 2-1', rev_id='2-1')
231
self.build_tree_contents([('br2/file', 'agreement\n')])
232
wt2.commit(message='rev 2-2', rev_id='2-2')
234
def test_merge_fetches_file_history(self):
235
"""Merge brings across file histories"""
236
br2 = Branch.open('br2')
237
br1 = Branch.open('br1')
238
wt2 = WorkingTree.open('br2').merge_from_branch(br1)
240
self.addCleanup(br2.unlock)
241
for rev_id, text in [('1-2', 'original from 1\n'),
242
('1-3', 'agreement\n'),
243
('2-1', 'contents in 2\n'),
244
('2-2', 'agreement\n')]:
245
self.assertEqualDiff(
246
br2.repository.revision_tree(
247
rev_id).get_file_text('this-file-id'), text)
250
class TestKnitToPackFetch(TestCaseWithTransport):
252
def find_get_record_stream(self, calls, expected_count=1):
253
"""In a list of calls, find the last 'get_record_stream'.
255
:param expected_count: The number of calls we should exepect to find.
256
If a different number is found, an assertion is raised.
258
get_record_call = None
261
if call[0] == 'get_record_stream':
263
get_record_call = call
264
self.assertEqual(expected_count, call_count)
265
return get_record_call
267
def test_fetch_with_deltas_no_delta_closure(self):
268
tree = self.make_branch_and_tree('source', format='dirstate')
269
target = self.make_repository('target', format='pack-0.92')
270
self.build_tree(['source/file'])
271
tree.set_root_id('root-id')
272
tree.add('file', 'file-id')
273
tree.commit('one', rev_id='rev-one')
274
source = tree.branch.repository
275
source.texts = versionedfile.RecordingVersionedFilesDecorator(
277
source.signatures = versionedfile.RecordingVersionedFilesDecorator(
279
source.revisions = versionedfile.RecordingVersionedFilesDecorator(
281
source.inventories = versionedfile.RecordingVersionedFilesDecorator(
284
self.assertTrue(target._format._fetch_uses_deltas)
285
target.fetch(source, revision_id='rev-one')
286
self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
287
target._format._fetch_order, False),
288
self.find_get_record_stream(source.texts.calls))
289
self.assertEqual(('get_record_stream', [('rev-one',)],
290
target._format._fetch_order, False),
291
self.find_get_record_stream(source.inventories.calls, 2))
292
self.assertEqual(('get_record_stream', [('rev-one',)],
293
target._format._fetch_order, False),
294
self.find_get_record_stream(source.revisions.calls))
295
# XXX: Signatures is special, and slightly broken. The
296
# standard item_keys_introduced_by actually does a lookup for every
297
# signature to see if it exists, rather than waiting to do them all at
298
# once at the end. The fetch code then does an all-at-once and just
299
# allows for some of them to be missing.
300
# So we know there will be extra calls, but the *last* one is the one
302
signature_calls = source.signatures.calls[-1:]
303
self.assertEqual(('get_record_stream', [('rev-one',)],
304
target._format._fetch_order, False),
305
self.find_get_record_stream(signature_calls))
307
def test_fetch_no_deltas_with_delta_closure(self):
308
tree = self.make_branch_and_tree('source', format='dirstate')
309
target = self.make_repository('target', format='pack-0.92')
310
self.build_tree(['source/file'])
311
tree.set_root_id('root-id')
312
tree.add('file', 'file-id')
313
tree.commit('one', rev_id='rev-one')
314
source = tree.branch.repository
315
source.texts = versionedfile.RecordingVersionedFilesDecorator(
317
source.signatures = versionedfile.RecordingVersionedFilesDecorator(
319
source.revisions = versionedfile.RecordingVersionedFilesDecorator(
321
source.inventories = versionedfile.RecordingVersionedFilesDecorator(
323
# XXX: This won't work in general, but for the dirstate format it does.
324
self.overrideAttr(target._format, '_fetch_uses_deltas', False)
325
target.fetch(source, revision_id='rev-one')
326
self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
327
target._format._fetch_order, True),
328
self.find_get_record_stream(source.texts.calls))
329
self.assertEqual(('get_record_stream', [('rev-one',)],
330
target._format._fetch_order, True),
331
self.find_get_record_stream(source.inventories.calls, 2))
332
self.assertEqual(('get_record_stream', [('rev-one',)],
333
target._format._fetch_order, True),
334
self.find_get_record_stream(source.revisions.calls))
335
# XXX: Signatures is special, and slightly broken. The
336
# standard item_keys_introduced_by actually does a lookup for every
337
# signature to see if it exists, rather than waiting to do them all at
338
# once at the end. The fetch code then does an all-at-once and just
339
# allows for some of them to be missing.
340
# So we know there will be extra calls, but the *last* one is the one
342
signature_calls = source.signatures.calls[-1:]
343
self.assertEqual(('get_record_stream', [('rev-one',)],
344
target._format._fetch_order, True),
345
self.find_get_record_stream(signature_calls))
347
def test_fetch_revisions_with_deltas_into_pack(self):
348
# See BUG #261339, dev versions of bzr could accidentally create deltas
349
# in revision texts in knit branches (when fetching from packs). So we
350
# ensure that *if* a knit repository has a delta in revisions, that it
351
# gets properly expanded back into a fulltext when stored in the pack
353
tree = self.make_branch_and_tree('source', format='dirstate')
354
target = self.make_repository('target', format='pack-0.92')
355
self.build_tree(['source/file'])
356
tree.set_root_id('root-id')
357
tree.add('file', 'file-id')
358
tree.commit('one', rev_id='rev-one')
359
# Hack the KVF for revisions so that it "accidentally" allows a delta
360
tree.branch.repository.revisions._max_delta_chain = 200
361
tree.commit('two', rev_id='rev-two')
362
source = tree.branch.repository
363
# Ensure that we stored a delta
365
self.addCleanup(source.unlock)
366
record = source.revisions.get_record_stream([('rev-two',)],
367
'unordered', False).next()
368
self.assertEqual('knit-delta-gz', record.storage_kind)
369
target.fetch(tree.branch.repository, revision_id='rev-two')
370
# The record should get expanded back to a fulltext
372
self.addCleanup(target.unlock)
373
record = target.revisions.get_record_stream([('rev-two',)],
374
'unordered', False).next()
375
self.assertEqual('knit-ft-gz', record.storage_kind)
377
def test_fetch_with_fallback_and_merge(self):
378
builder = self.make_branch_builder('source', format='pack-0.92')
379
builder.start_series()
390
# A & B are present in the base (stacked-on) repository, A-E are
391
# present in the source.
392
# This reproduces bug #304841
393
# We need a large enough inventory that total size of compressed deltas
394
# is shorter than the size of a compressed fulltext. We have to use
395
# random ids because otherwise the inventory fulltext compresses too
396
# well and the deltas get bigger.
398
('add', ('', 'TREE_ROOT', 'directory', None))]
400
fname = 'file%03d' % (i,)
401
fileid = '%s-%s' % (fname, osutils.rand_chars(64))
402
to_add.append(('add', (fname, fileid, 'file', 'content\n')))
403
builder.build_snapshot('A', None, to_add)
404
builder.build_snapshot('B', ['A'], [])
405
builder.build_snapshot('C', ['A'], [])
406
builder.build_snapshot('D', ['C'], [])
407
builder.build_snapshot('E', ['D'], [])
408
builder.build_snapshot('F', ['E', 'B'], [])
409
builder.finish_series()
410
source_branch = builder.get_branch()
411
source_branch.bzrdir.sprout('base', revision_id='B')
412
target_branch = self.make_branch('target', format='1.6')
413
target_branch.set_stacked_on_url('../base')
414
source = source_branch.repository
416
self.addCleanup(source.unlock)
417
source.inventories = versionedfile.OrderingVersionedFilesDecorator(
419
key_priority={('E',): 1, ('D',): 2, ('C',): 4,
421
# Ensure that the content is yielded in the proper order, and given as
423
records = [(record.key, record.storage_kind)
424
for record in source.inventories.get_record_stream(
425
[('D',), ('C',), ('E',), ('F',)], 'unordered', False)]
426
self.assertEqual([(('E',), 'knit-delta-gz'), (('D',), 'knit-delta-gz'),
427
(('F',), 'knit-delta-gz'), (('C',), 'knit-delta-gz')],
430
target_branch.lock_write()
431
self.addCleanup(target_branch.unlock)
432
target = target_branch.repository
433
target.fetch(source, revision_id='F')
434
# 'C' should be expanded to a fulltext, but D and E should still be
436
stream = target.inventories.get_record_stream(
437
[('C',), ('D',), ('E',), ('F',)],
439
kinds = dict((record.key, record.storage_kind) for record in stream)
440
self.assertEqual({('C',): 'knit-ft-gz', ('D',): 'knit-delta-gz',
441
('E',): 'knit-delta-gz', ('F',): 'knit-delta-gz'},
445
class Test1To2Fetch(TestCaseWithTransport):
446
"""Tests for Model1To2 failure modes"""
448
def make_tree_and_repo(self):
449
self.tree = self.make_branch_and_tree('tree', format='pack-0.92')
450
self.repo = self.make_repository('rich-repo', format='rich-root-pack')
451
self.repo.lock_write()
452
self.addCleanup(self.repo.unlock)
454
def do_fetch_order_test(self, first, second):
455
"""Test that fetch works no matter what the set order of revision is.
457
This test depends on the order of items in a set, which is
458
implementation-dependant, so we test A, B and then B, A.
460
self.make_tree_and_repo()
461
self.tree.commit('Commit 1', rev_id=first)
462
self.tree.commit('Commit 2', rev_id=second)
463
self.repo.fetch(self.tree.branch.repository, second)
465
def test_fetch_order_AB(self):
466
"""See do_fetch_order_test"""
467
self.do_fetch_order_test('A', 'B')
469
def test_fetch_order_BA(self):
470
"""See do_fetch_order_test"""
471
self.do_fetch_order_test('B', 'A')
473
def get_parents(self, file_id, revision_id):
474
self.repo.lock_read()
476
parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
477
return parent_map[(file_id, revision_id)]
481
def test_fetch_ghosts(self):
482
self.make_tree_and_repo()
483
self.tree.commit('first commit', rev_id='left-parent')
484
self.tree.add_parent_tree_id('ghost-parent')
485
fork = self.tree.bzrdir.sprout('fork', 'null:').open_workingtree()
486
fork.commit('not a ghost', rev_id='not-ghost-parent')
487
self.tree.branch.repository.fetch(fork.branch.repository,
489
self.tree.add_parent_tree_id('not-ghost-parent')
490
self.tree.commit('second commit', rev_id='second-id')
491
self.repo.fetch(self.tree.branch.repository, 'second-id')
492
root_id = self.tree.get_root_id()
494
((root_id, 'left-parent'), (root_id, 'not-ghost-parent')),
495
self.get_parents(root_id, 'second-id'))
497
def make_two_commits(self, change_root, fetch_twice):
498
self.make_tree_and_repo()
499
self.tree.commit('first commit', rev_id='first-id')
501
self.tree.set_root_id('unique-id')
502
self.tree.commit('second commit', rev_id='second-id')
504
self.repo.fetch(self.tree.branch.repository, 'first-id')
505
self.repo.fetch(self.tree.branch.repository, 'second-id')
507
def test_fetch_changed_root(self):
508
self.make_two_commits(change_root=True, fetch_twice=False)
509
self.assertEqual((), self.get_parents('unique-id', 'second-id'))
511
def test_two_fetch_changed_root(self):
512
self.make_two_commits(change_root=True, fetch_twice=True)
513
self.assertEqual((), self.get_parents('unique-id', 'second-id'))
515
def test_two_fetches(self):
516
self.make_two_commits(change_root=False, fetch_twice=True)
517
self.assertEqual((('TREE_ROOT', 'first-id'),),
518
self.get_parents('TREE_ROOT', 'second-id'))
35
br_a, br_b = make_branches()
36
assert not has_revision(br_b, br_a.revision_history()[3])
37
assert has_revision(br_b, br_a.revision_history()[2])
38
assert len(br_b.revision_history()) == 7
39
assert greedy_fetch(br_b, br_a, br_a.revision_history()[2])[0] == 0
41
# greedy_fetch is not supposed to alter the revision history
42
assert len(br_b.revision_history()) == 7
43
assert not has_revision(br_b, br_a.revision_history()[3])
45
assert len(br_b.revision_history()) == 7
46
assert greedy_fetch(br_b, br_a, br_a.revision_history()[3])[0] == 1
47
assert has_revision(br_b, br_a.revision_history()[3])
48
assert not has_revision(br_a, br_b.revision_history()[3])
49
assert not has_revision(br_a, br_b.revision_history()[4])
51
# When a non-branch ancestor is missing, it should be a failure, not
53
br_a4 = new_branch('br_a4')
54
count, failures = greedy_fetch(br_a4, br_a)
56
assert failures == set((br_b.revision_history()[4],
57
br_b.revision_history()[5]))
59
assert greedy_fetch(br_a, br_b)[0] == 4
60
assert has_revision(br_a, br_b.revision_history()[3])
61
assert has_revision(br_a, br_b.revision_history()[4])
63
br_b2 = new_branch('br_b2')
64
assert greedy_fetch(br_b2, br_b)[0] == 7
65
assert has_revision(br_b2, br_b.revision_history()[4])
66
assert has_revision(br_b2, br_a.revision_history()[2])
67
assert not has_revision(br_b2, br_a.revision_history()[3])
69
br_a2 = new_branch('br_a2')
70
assert greedy_fetch(br_a2, br_a)[0] == 9
71
assert has_revision(br_a2, br_b.revision_history()[4])
72
assert has_revision(br_a2, br_a.revision_history()[3])
74
br_a3 = new_branch('br_a3')
75
assert greedy_fetch(br_a3, br_a2)[0] == 0
76
for revno in range(4):
77
assert not has_revision(br_a3, br_a.revision_history()[revno])
78
assert greedy_fetch(br_a3, br_a2, br_a.revision_history()[2])[0] == 3
79
fetched = greedy_fetch(br_a3, br_a2, br_a.revision_history()[3])[0]
80
assert fetched == 3, "fetched %d instead of 3" % fetched
81
# InstallFailed should be raised if the branch is missing the revision
83
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
85
# InstallFailed should be raised if the branch is missing a revision
86
# from its own revision history
87
br_a2.append_revision('a-b-c')
88
self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
93
if __name__ == '__main__':
95
sys.exit(unittest.run_suite(unittest.makeSuite()))