14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
revision as _mod_revision,
26
from ..branch import Branch
27
from ..bzr import knitrepo
28
from . import TestCaseWithTransport
29
from .test_revision import make_branches
30
from ..upgrade import Convert
31
from ..workingtree import WorkingTree
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
33
39
# These tests are a bit old; please instead add new tests into
34
40
# per_interrepository/ so they'll run on all relevant
38
44
def has_revision(branch, revision_id):
39
45
return branch.repository.has_revision(revision_id)
42
def revision_history(branch):
45
graph = branch.repository.get_graph()
46
history = list(graph.iter_lefthand_ancestry(branch.last_revision(),
47
[_mod_revision.NULL_REVISION]))
54
47
def fetch_steps(self, br_a, br_b, writable_a):
55
48
"""A foreign test method for testing fetch locally and remotely."""
57
50
# TODO RBC 20060201 make this a repository test.
58
51
repo_b = br_b.repository
59
self.assertFalse(repo_b.has_revision(revision_history(br_a)[3]))
60
self.assertTrue(repo_b.has_revision(revision_history(br_a)[2]))
61
self.assertEqual(len(revision_history(br_b)), 7)
62
br_b.fetch(br_a, revision_history(br_a)[2])
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])
63
56
# branch.fetch is not supposed to alter the revision history
64
self.assertEqual(len(revision_history(br_b)), 7)
65
self.assertFalse(repo_b.has_revision(revision_history(br_a)[3]))
57
self.assertEquals(len(br_b.revision_history()), 7)
58
self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
67
60
# fetching the next revision up in sample data copies one revision
68
br_b.fetch(br_a, revision_history(br_a)[3])
69
self.assertTrue(repo_b.has_revision(revision_history(br_a)[3]))
70
self.assertFalse(has_revision(br_a, revision_history(br_b)[6]))
71
self.assertTrue(br_a.repository.has_revision(revision_history(br_b)[5]))
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]))
73
66
# When a non-branch ancestor is missing, it should be unlisted...
74
67
# as its not reference from the inventory weave.
78
71
writable_a.fetch(br_b)
79
self.assertTrue(has_revision(br_a, revision_history(br_b)[3]))
80
self.assertTrue(has_revision(br_a, revision_history(br_b)[4]))
72
self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
73
self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
82
75
br_b2 = self.make_branch('br_b2')
84
self.assertTrue(has_revision(br_b2, revision_history(br_b)[4]))
85
self.assertTrue(has_revision(br_b2, revision_history(br_a)[2]))
86
self.assertFalse(has_revision(br_b2, revision_history(br_a)[3]))
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]))
88
81
br_a2 = self.make_branch('br_a2')
90
self.assertTrue(has_revision(br_a2, revision_history(br_b)[4]))
91
self.assertTrue(has_revision(br_a2, revision_history(br_a)[3]))
92
self.assertTrue(has_revision(br_a2, revision_history(br_a)[2]))
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]))
94
87
br_a3 = self.make_branch('br_a3')
95
88
# pulling a branch with no revisions grabs nothing, regardless of
141
134
# root revision to change for each commit, even though the content,
142
135
# parent, name, and other attributes are unchanged.
143
136
tree = self.make_branch_and_tree('tree', knit1_format)
144
tree.set_root_id(b'tree-root')
145
tree.commit('rev1', rev_id=b'rev1')
146
tree.commit('rev2', rev_id=b'rev2')
137
tree.set_root_id('tree-root')
138
tree.commit('rev1', rev_id='rev1')
139
tree.commit('rev2', rev_id='rev2')
148
141
# Now we convert it to a knit2 repository so that it has a root knit
149
142
Convert(tree.basedir, knit2_format)
150
143
tree = WorkingTree.open(tree.basedir)
151
144
branch = self.make_branch('branch', format=knit2_format)
152
branch.pull(tree.branch, stop_revision=b'rev1')
145
branch.pull(tree.branch, stop_revision='rev1')
153
146
repo = branch.repository
156
149
# Make sure fetch retrieved only what we requested
157
self.assertEqual({(b'tree-root', b'rev1'): ()},
158
repo.texts.get_parent_map(
159
[(b'tree-root', b'rev1'), (b'tree-root', b'rev2')]))
150
self.assertEqual({('tree-root', 'rev1'):()},
151
repo.texts.get_parent_map(
152
[('tree-root', 'rev1'), ('tree-root', 'rev2')]))
162
155
branch.pull(tree.branch)
167
160
# Make sure fetch retrieved only what we requested
168
self.assertEqual({(b'tree-root', b'rev2'): ((b'tree-root', b'rev1'),)},
169
repo.texts.get_parent_map([(b'tree-root', b'rev2')]))
161
self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
162
repo.texts.get_parent_map([('tree-root', 'rev2')]))
173
166
def test_fetch_incompatible(self):
174
167
knit_tree = self.make_branch_and_tree('knit', format='knit')
175
168
knit3_tree = self.make_branch_and_tree('knit3',
176
format='dirstate-with-subtree')
169
format='dirstate-with-subtree')
177
170
knit3_tree.commit('blah')
178
171
e = self.assertRaises(errors.IncompatibleRepositories,
179
172
knit_tree.branch.fetch, knit3_tree.branch)
180
173
self.assertContainsRe(str(e),
181
r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
182
r"different rich-root support")
174
r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
175
r"different rich-root support")
185
178
class TestMergeFetch(TestCaseWithTransport):
188
181
"""Merge brings across history from unrelated source"""
189
182
wt1 = self.make_branch_and_tree('br1')
191
wt1.commit(message='rev 1-1', rev_id=b'1-1')
192
wt1.commit(message='rev 1-2', rev_id=b'1-2')
184
wt1.commit(message='rev 1-1', rev_id='1-1')
185
wt1.commit(message='rev 1-2', rev_id='1-2')
193
186
wt2 = self.make_branch_and_tree('br2')
195
wt2.commit(message='rev 2-1', rev_id=b'2-1')
196
wt2.merge_from_branch(br1, from_revision=b'null:')
188
wt2.commit(message='rev 2-1', rev_id='2-1')
189
wt2.merge_from_branch(br1, from_revision='null:')
197
190
self._check_revs_present(br2)
199
192
def test_merge_fetches(self):
200
193
"""Merge brings across history from source"""
201
194
wt1 = self.make_branch_and_tree('br1')
203
wt1.commit(message='rev 1-1', rev_id=b'1-1')
204
dir_2 = br1.controldir.sprout('br2')
196
wt1.commit(message='rev 1-1', rev_id='1-1')
197
dir_2 = br1.bzrdir.sprout('br2')
205
198
br2 = dir_2.open_branch()
206
wt1.commit(message='rev 1-2', rev_id=b'1-2')
199
wt1.commit(message='rev 1-2', rev_id='1-2')
207
200
wt2 = dir_2.open_workingtree()
208
wt2.commit(message='rev 2-1', rev_id=b'2-1')
201
wt2.commit(message='rev 2-1', rev_id='2-1')
209
202
wt2.merge_from_branch(br1)
210
203
self._check_revs_present(br2)
212
205
def _check_revs_present(self, br2):
213
for rev_id in [b'1-1', b'1-2', b'2-1']:
206
for rev_id in '1-1', '1-2', '2-1':
214
207
self.assertTrue(br2.repository.has_revision(rev_id))
215
208
rev = br2.repository.get_revision(rev_id)
216
209
self.assertEqual(rev.revision_id, rev_id)
223
216
super(TestMergeFileHistory, self).setUp()
224
217
wt1 = self.make_branch_and_tree('br1')
226
self.build_tree_contents([('br1/file', b'original contents\n')])
227
wt1.add('file', b'this-file-id')
228
wt1.commit(message='rev 1-1', rev_id=b'1-1')
229
dir_2 = br1.controldir.sprout('br2')
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')
230
223
br2 = dir_2.open_branch()
231
224
wt2 = dir_2.open_workingtree()
232
self.build_tree_contents([('br1/file', b'original from 1\n')])
233
wt1.commit(message='rev 1-2', rev_id=b'1-2')
234
self.build_tree_contents([('br1/file', b'agreement\n')])
235
wt1.commit(message='rev 1-3', rev_id=b'1-3')
236
self.build_tree_contents([('br2/file', b'contents in 2\n')])
237
wt2.commit(message='rev 2-1', rev_id=b'2-1')
238
self.build_tree_contents([('br2/file', b'agreement\n')])
239
wt2.commit(message='rev 2-2', rev_id=b'2-2')
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')
241
234
def test_merge_fetches_file_history(self):
242
235
"""Merge brings across file histories"""
275
268
tree = self.make_branch_and_tree('source', format='dirstate')
276
269
target = self.make_repository('target', format='pack-0.92')
277
270
self.build_tree(['source/file'])
278
tree.set_root_id(b'root-id')
279
tree.add('file', b'file-id')
280
tree.commit('one', rev_id=b'rev-one')
271
tree.set_root_id('root-id')
272
tree.add('file', 'file-id')
273
tree.commit('one', rev_id='rev-one')
281
274
source = tree.branch.repository
282
275
source.texts = versionedfile.RecordingVersionedFilesDecorator(
284
277
source.signatures = versionedfile.RecordingVersionedFilesDecorator(
286
279
source.revisions = versionedfile.RecordingVersionedFilesDecorator(
288
281
source.inventories = versionedfile.RecordingVersionedFilesDecorator(
291
284
self.assertTrue(target._format._fetch_uses_deltas)
292
target.fetch(source, revision_id=b'rev-one')
293
self.assertEqual(('get_record_stream', [(b'file-id', b'rev-one')],
285
target.fetch(source, revision_id='rev-one')
286
self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
294
287
target._format._fetch_order, False),
295
288
self.find_get_record_stream(source.texts.calls))
296
self.assertEqual(('get_record_stream', [(b'rev-one',)],
297
target._format._fetch_order, False),
298
self.find_get_record_stream(source.inventories.calls, 2))
299
self.assertEqual(('get_record_stream', [(b'rev-one',)],
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',)],
300
293
target._format._fetch_order, False),
301
294
self.find_get_record_stream(source.revisions.calls))
302
295
# XXX: Signatures is special, and slightly broken. The
315
308
tree = self.make_branch_and_tree('source', format='dirstate')
316
309
target = self.make_repository('target', format='pack-0.92')
317
310
self.build_tree(['source/file'])
318
tree.set_root_id(b'root-id')
319
tree.add('file', b'file-id')
320
tree.commit('one', rev_id=b'rev-one')
311
tree.set_root_id('root-id')
312
tree.add('file', 'file-id')
313
tree.commit('one', rev_id='rev-one')
321
314
source = tree.branch.repository
322
315
source.texts = versionedfile.RecordingVersionedFilesDecorator(
324
317
source.signatures = versionedfile.RecordingVersionedFilesDecorator(
326
319
source.revisions = versionedfile.RecordingVersionedFilesDecorator(
328
321
source.inventories = versionedfile.RecordingVersionedFilesDecorator(
330
323
# XXX: This won't work in general, but for the dirstate format it does.
331
324
self.overrideAttr(target._format, '_fetch_uses_deltas', False)
332
target.fetch(source, revision_id=b'rev-one')
333
self.assertEqual(('get_record_stream', [(b'file-id', b'rev-one')],
325
target.fetch(source, revision_id='rev-one')
326
self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
334
327
target._format._fetch_order, True),
335
328
self.find_get_record_stream(source.texts.calls))
336
self.assertEqual(('get_record_stream', [(b'rev-one',)],
337
target._format._fetch_order, True),
338
self.find_get_record_stream(source.inventories.calls, 2))
339
self.assertEqual(('get_record_stream', [(b'rev-one',)],
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',)],
340
333
target._format._fetch_order, True),
341
334
self.find_get_record_stream(source.revisions.calls))
342
335
# XXX: Signatures is special, and slightly broken. The
360
353
tree = self.make_branch_and_tree('source', format='dirstate')
361
354
target = self.make_repository('target', format='pack-0.92')
362
355
self.build_tree(['source/file'])
363
tree.set_root_id(b'root-id')
364
tree.add('file', b'file-id')
365
tree.commit('one', rev_id=b'rev-one')
356
tree.set_root_id('root-id')
357
tree.add('file', 'file-id')
358
tree.commit('one', rev_id='rev-one')
366
359
# Hack the KVF for revisions so that it "accidentally" allows a delta
367
360
tree.branch.repository.revisions._max_delta_chain = 200
368
tree.commit('two', rev_id=b'rev-two')
361
tree.commit('two', rev_id='rev-two')
369
362
source = tree.branch.repository
370
363
# Ensure that we stored a delta
371
364
source.lock_read()
372
365
self.addCleanup(source.unlock)
373
record = next(source.revisions.get_record_stream([(b'rev-two',)],
366
record = source.revisions.get_record_stream([('rev-two',)],
367
'unordered', False).next()
375
368
self.assertEqual('knit-delta-gz', record.storage_kind)
376
target.fetch(tree.branch.repository, revision_id=b'rev-two')
369
target.fetch(tree.branch.repository, revision_id='rev-two')
377
370
# The record should get expanded back to a fulltext
378
371
target.lock_read()
379
372
self.addCleanup(target.unlock)
380
record = next(target.revisions.get_record_stream([(b'rev-two',)],
373
record = target.revisions.get_record_stream([('rev-two',)],
374
'unordered', False).next()
382
375
self.assertEqual('knit-ft-gz', record.storage_kind)
384
377
def test_fetch_with_fallback_and_merge(self):
402
395
# random ids because otherwise the inventory fulltext compresses too
403
396
# well and the deltas get bigger.
405
('add', ('', b'TREE_ROOT', 'directory', None))]
398
('add', ('', 'TREE_ROOT', 'directory', None))]
407
400
fname = 'file%03d' % (i,)
409
(fname, osutils.rand_chars(64))).encode('ascii')
410
to_add.append(('add', (fname, fileid, 'file', b'content\n')))
411
builder.build_snapshot(None, to_add, revision_id=b'A')
412
builder.build_snapshot([b'A'], [], revision_id=b'B')
413
builder.build_snapshot([b'A'], [], revision_id=b'C')
414
builder.build_snapshot([b'C'], [], revision_id=b'D')
415
builder.build_snapshot([b'D'], [], revision_id=b'E')
416
builder.build_snapshot([b'E', b'B'], [], revision_id=b'F')
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'], [])
417
409
builder.finish_series()
418
410
source_branch = builder.get_branch()
419
source_branch.controldir.sprout('base', revision_id=b'B')
411
source_branch.bzrdir.sprout('base', revision_id='B')
420
412
target_branch = self.make_branch('target', format='1.6')
421
413
target_branch.set_stacked_on_url('../base')
422
414
source = source_branch.repository
423
415
source.lock_read()
424
416
self.addCleanup(source.unlock)
425
417
source.inventories = versionedfile.OrderingVersionedFilesDecorator(
427
key_priority={(b'E',): 1, (b'D',): 2, (b'C',): 4,
419
key_priority={('E',): 1, ('D',): 2, ('C',): 4,
429
421
# Ensure that the content is yielded in the proper order, and given as
430
422
# the expected kinds
431
423
records = [(record.key, record.storage_kind)
432
424
for record in source.inventories.get_record_stream(
433
[(b'D',), (b'C',), (b'E',), (b'F',)], 'unordered', False)]
434
self.assertEqual([((b'E',), 'knit-delta-gz'), ((b'D',), 'knit-delta-gz'),
435
((b'F',), 'knit-delta-gz'), ((b'C',), 'knit-delta-gz')],
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')],
438
430
target_branch.lock_write()
439
431
self.addCleanup(target_branch.unlock)
440
432
target = target_branch.repository
441
target.fetch(source, revision_id=b'F')
433
target.fetch(source, revision_id='F')
442
434
# 'C' should be expanded to a fulltext, but D and E should still be
444
436
stream = target.inventories.get_record_stream(
445
[(b'C',), (b'D',), (b'E',), (b'F',)],
437
[('C',), ('D',), ('E',), ('F',)],
446
438
'unordered', False)
447
439
kinds = dict((record.key, record.storage_kind) for record in stream)
448
self.assertEqual({(b'C',): 'knit-ft-gz', (b'D',): 'knit-delta-gz',
449
(b'E',): 'knit-delta-gz', (b'F',): 'knit-delta-gz'},
440
self.assertEqual({('C',): 'knit-ft-gz', ('D',): 'knit-delta-gz',
441
('E',): 'knit-delta-gz', ('F',): 'knit-delta-gz'},
473
465
def test_fetch_order_AB(self):
474
466
"""See do_fetch_order_test"""
475
self.do_fetch_order_test(b'A', b'B')
467
self.do_fetch_order_test('A', 'B')
477
469
def test_fetch_order_BA(self):
478
470
"""See do_fetch_order_test"""
479
self.do_fetch_order_test(b'B', b'A')
471
self.do_fetch_order_test('B', 'A')
481
473
def get_parents(self, file_id, revision_id):
482
474
self.repo.lock_read()
484
parent_map = self.repo.texts.get_parent_map(
485
[(file_id, revision_id)])
476
parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
486
477
return parent_map[(file_id, revision_id)]
488
479
self.repo.unlock()
490
481
def test_fetch_ghosts(self):
491
482
self.make_tree_and_repo()
492
self.tree.commit('first commit', rev_id=b'left-parent')
493
self.tree.add_parent_tree_id(b'ghost-parent')
494
fork = self.tree.controldir.sprout('fork', b'null:').open_workingtree()
495
fork.commit('not a ghost', rev_id=b'not-ghost-parent')
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')
496
487
self.tree.branch.repository.fetch(fork.branch.repository,
498
self.tree.add_parent_tree_id(b'not-ghost-parent')
499
self.tree.commit('second commit', rev_id=b'second-id')
500
self.repo.fetch(self.tree.branch.repository, b'second-id')
501
root_id = self.tree.path2id('')
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()
502
493
self.assertEqual(
503
((root_id, b'left-parent'), (root_id, b'not-ghost-parent')),
504
self.get_parents(root_id, b'second-id'))
494
((root_id, 'left-parent'), (root_id, 'not-ghost-parent')),
495
self.get_parents(root_id, 'second-id'))
506
497
def make_two_commits(self, change_root, fetch_twice):
507
498
self.make_tree_and_repo()
508
self.tree.commit('first commit', rev_id=b'first-id')
499
self.tree.commit('first commit', rev_id='first-id')
510
self.tree.set_root_id(b'unique-id')
511
self.tree.commit('second commit', rev_id=b'second-id')
501
self.tree.set_root_id('unique-id')
502
self.tree.commit('second commit', rev_id='second-id')
513
self.repo.fetch(self.tree.branch.repository, b'first-id')
514
self.repo.fetch(self.tree.branch.repository, b'second-id')
504
self.repo.fetch(self.tree.branch.repository, 'first-id')
505
self.repo.fetch(self.tree.branch.repository, 'second-id')
516
507
def test_fetch_changed_root(self):
517
508
self.make_two_commits(change_root=True, fetch_twice=False)
518
self.assertEqual((), self.get_parents(b'unique-id', b'second-id'))
509
self.assertEqual((), self.get_parents('unique-id', 'second-id'))
520
511
def test_two_fetch_changed_root(self):
521
512
self.make_two_commits(change_root=True, fetch_twice=True)
522
self.assertEqual((), self.get_parents(b'unique-id', b'second-id'))
513
self.assertEqual((), self.get_parents('unique-id', 'second-id'))
524
515
def test_two_fetches(self):
525
516
self.make_two_commits(change_root=False, fetch_twice=True)
526
self.assertEqual(((b'TREE_ROOT', b'first-id'),),
527
self.get_parents(b'TREE_ROOT', b'second-id'))
517
self.assertEqual((('TREE_ROOT', 'first-id'),),
518
self.get_parents('TREE_ROOT', 'second-id'))