~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: Dmitry Vasiliev
  • Date: 2007-03-07 13:47:47 UTC
  • mto: (2327.1.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2328.
  • Revision ID: dima@hlabs.spb.ru-20070307134747-clcmwlfck4g9yqh3
Updated note about registry.Registry

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
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
12
12
#
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
16
16
 
17
17
import os
18
18
import re
19
19
import sys
20
20
 
21
 
import bzrlib
22
 
from bzrlib import (
23
 
    bzrdir,
24
 
    errors,
25
 
    osutils,
26
 
    merge,
27
 
    repository,
28
 
    versionedfile,
29
 
    )
 
21
from bzrlib import bzrdir, repository
30
22
from bzrlib.branch import Branch
31
23
from bzrlib.bzrdir import BzrDir
 
24
from bzrlib.builtins import merge
 
25
import bzrlib.errors
32
26
from bzrlib.repofmt import knitrepo
33
27
from bzrlib.tests import TestCaseWithTransport
34
 
from bzrlib.tests.http_utils import TestCaseWithWebserver
 
28
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
35
29
from bzrlib.tests.test_revision import make_branches
36
30
from bzrlib.trace import mutter
37
31
from bzrlib.upgrade import Convert
38
32
from bzrlib.workingtree import WorkingTree
39
33
 
40
 
# These tests are a bit old; please instead add new tests into
41
 
# interrepository_implementations/ so they'll run on all relevant
42
 
# combinations.
43
 
 
44
34
 
45
35
def has_revision(branch, revision_id):
46
36
    return branch.repository.has_revision(revision_id)
47
37
 
48
38
def fetch_steps(self, br_a, br_b, writable_a):
49
39
    """A foreign test method for testing fetch locally and remotely."""
50
 
 
 
40
     
51
41
    # TODO RBC 20060201 make this a repository test.
52
42
    repo_b = br_b.repository
53
43
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
54
44
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
55
45
    self.assertEquals(len(br_b.revision_history()), 7)
56
 
    br_b.fetch(br_a, br_a.revision_history()[2])
 
46
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
57
47
    # branch.fetch is not supposed to alter the revision history
58
48
    self.assertEquals(len(br_b.revision_history()), 7)
59
49
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
60
50
 
61
51
    # fetching the next revision up in sample data copies one revision
62
 
    br_b.fetch(br_a, br_a.revision_history()[3])
 
52
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
63
53
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
64
54
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
65
55
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
67
57
    # When a non-branch ancestor is missing, it should be unlisted...
68
58
    # as its not reference from the inventory weave.
69
59
    br_b4 = self.make_branch('br_4')
70
 
    br_b4.fetch(br_b)
 
60
    count, failures = br_b4.fetch(br_b)
 
61
    self.assertEqual(count, 7)
 
62
    self.assertEqual(failures, [])
71
63
 
72
 
    writable_a.fetch(br_b)
 
64
    self.assertEqual(writable_a.fetch(br_b)[0], 1)
73
65
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
74
66
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
75
 
 
 
67
        
76
68
    br_b2 = self.make_branch('br_b2')
77
 
    br_b2.fetch(br_b)
 
69
    self.assertEquals(br_b2.fetch(br_b)[0], 7)
78
70
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
79
71
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
80
72
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
81
73
 
82
74
    br_a2 = self.make_branch('br_a2')
83
 
    br_a2.fetch(br_a)
 
75
    self.assertEquals(br_a2.fetch(br_a)[0], 9)
84
76
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
85
77
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
86
78
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
87
79
 
88
80
    br_a3 = self.make_branch('br_a3')
89
 
    # pulling a branch with no revisions grabs nothing, regardless of
 
81
    # pulling a branch with no revisions grabs nothing, regardless of 
90
82
    # whats in the inventory.
91
 
    br_a3.fetch(br_a2)
 
83
    self.assertEquals(br_a3.fetch(br_a2)[0], 0)
92
84
    for revno in range(4):
93
85
        self.assertFalse(
94
86
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
95
 
    br_a3.fetch(br_a2, br_a.revision_history()[2])
 
87
    self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
96
88
    # pull the 3 revisions introduced by a@u-0-3
97
 
    br_a3.fetch(br_a2, br_a.revision_history()[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)
98
91
    # InstallFailed should be raised if the branch is missing the revision
99
92
    # that was requested.
100
 
    self.assertRaises(errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
 
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)
101
98
 
102
 
    # TODO: Test trying to fetch from a branch that points to a revision not
103
 
    # actually present in its repository.  Not every branch format allows you
104
 
    # to directly point to such revisions, so it's a bit complicated to
105
 
    # construct.  One way would be to uncommit and gc the revision, but not
106
 
    # every branch supports that.  -- mbp 20070814
 
99
    # TODO: ADHB 20070116 Perhaps set_last_revision shouldn't accept
 
100
    #       revisions which are not present?  In that case, this test
 
101
    #       must be rewritten.
 
102
    #
 
103
    #       RBC 20060403 the way to do this is to uncommit the revision from
 
104
    #       the repository after the commit
107
105
 
108
106
    #TODO: test that fetch correctly does reweaving when needed. RBC 20051008
109
 
    # Note that this means - updating the weave when ghosts are filled in to
 
107
    # Note that this means - updating the weave when ghosts are filled in to 
110
108
    # add the right parents.
111
109
 
112
110
 
114
112
 
115
113
    def test_fetch(self):
116
114
        #highest indices a: 5, b: 7
117
 
        br_a, br_b = make_branches(self, format='dirstate-tags')
 
115
        br_a, br_b = make_branches(self)
118
116
        fetch_steps(self, br_a, br_b, br_a)
119
117
 
120
118
    def test_fetch_self(self):
121
119
        wt = self.make_branch_and_tree('br')
122
 
        wt.branch.fetch(wt.branch)
 
120
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
123
121
 
124
122
    def test_fetch_root_knit(self):
125
123
        """Ensure that knit2.fetch() updates the root knit
126
 
 
 
124
        
127
125
        This tests the case where the root has a new revision, but there are no
128
126
        corresponding filename, parent, contents or other changes.
129
127
        """
145
143
        branch = self.make_branch('branch', format=knit2_format)
146
144
        branch.pull(tree.branch, stop_revision='rev1')
147
145
        repo = branch.repository
148
 
        repo.lock_read()
149
 
        try:
150
 
            # Make sure fetch retrieved only what we requested
151
 
            self.assertEqual({('tree-root', 'rev1'):()},
152
 
                repo.texts.get_parent_map(
153
 
                    [('tree-root', 'rev1'), ('tree-root', 'rev2')]))
154
 
        finally:
155
 
            repo.unlock()
 
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)
156
151
        branch.pull(tree.branch)
 
152
        root_knit = repo.weave_store.get_weave('tree-root',
 
153
                                                repo.get_transaction())
157
154
        # Make sure that the next revision in the root knit was retrieved,
158
155
        # even though the text, name, parent_id, etc., were unchanged.
159
 
        repo.lock_read()
160
 
        try:
161
 
            # Make sure fetch retrieved only what we requested
162
 
            self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
163
 
                repo.texts.get_parent_map([('tree-root', 'rev2')]))
164
 
        finally:
165
 
            repo.unlock()
166
 
 
167
 
    def test_fetch_incompatible(self):
168
 
        knit_tree = self.make_branch_and_tree('knit', format='knit')
169
 
        knit3_tree = self.make_branch_and_tree('knit3',
170
 
            format='dirstate-with-subtree')
171
 
        knit3_tree.commit('blah')
172
 
        e = self.assertRaises(errors.IncompatibleRepositories,
173
 
                              knit_tree.branch.fetch, knit3_tree.branch)
174
 
        self.assertContainsRe(str(e),
175
 
            r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
176
 
            r"different rich-root support")
 
156
        self.assertTrue('rev2' in root_knit)
177
157
 
178
158
 
179
159
class TestMergeFetch(TestCaseWithTransport):
187
167
        wt2 = self.make_branch_and_tree('br2')
188
168
        br2 = wt2.branch
189
169
        wt2.commit(message='rev 2-1', rev_id='2-1')
190
 
        wt2.merge_from_branch(br1, from_revision='null:')
 
170
        merge(other_revision=['br1', -1], base_revision=['br1', 0],
 
171
              this_dir='br2')
191
172
        self._check_revs_present(br2)
192
173
 
193
174
    def test_merge_fetches(self):
198
179
        dir_2 = br1.bzrdir.sprout('br2')
199
180
        br2 = dir_2.open_branch()
200
181
        wt1.commit(message='rev 1-2', rev_id='1-2')
201
 
        wt2 = dir_2.open_workingtree()
202
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
203
 
        wt2.merge_from_branch(br1)
 
182
        dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
 
183
        merge(other_revision=['br1', -1], base_revision=[None, None], 
 
184
              this_dir='br2')
204
185
        self._check_revs_present(br2)
205
186
 
206
187
    def _check_revs_present(self, br2):
235
216
    def test_merge_fetches_file_history(self):
236
217
        """Merge brings across file histories"""
237
218
        br2 = Branch.open('br2')
238
 
        br1 = Branch.open('br1')
239
 
        wt2 = WorkingTree.open('br2').merge_from_branch(br1)
240
 
        br2.lock_read()
241
 
        self.addCleanup(br2.unlock)
 
219
        merge(other_revision=['br1', -1], base_revision=[None, None], 
 
220
              this_dir='br2')
242
221
        for rev_id, text in [('1-2', 'original from 1\n'),
243
222
                             ('1-3', 'agreement\n'),
244
223
                             ('2-1', 'contents in 2\n'),
272
251
 
273
252
    def test_weaves_are_retrieved_once(self):
274
253
        self.build_tree(("source/", "source/file", "target/"))
275
 
        # This test depends on knit dasta storage.
276
 
        wt = self.make_branch_and_tree('source', format='dirstate-tags')
 
254
        wt = self.make_branch_and_tree('source')
277
255
        branch = wt.branch
278
256
        wt.add(["file"], ["id"])
279
257
        wt.commit("added file")
280
 
        open("source/file", 'w').write("blah\n")
 
258
        print >>open("source/file", 'w'), "blah"
281
259
        wt.commit("changed file")
282
260
        target = BzrDir.create_branch_and_repo("target/")
283
261
        source = Branch.open(self.get_readonly_url("source/"))
284
 
        target.fetch(source)
285
 
        # this is the path to the literal file. As format changes
 
262
        self.assertEqual(target.fetch(source), (2, []))
 
263
        # this is the path to the literal file. As format changes 
286
264
        # occur it needs to be updated. FIXME: ask the store for the
287
265
        # path.
288
266
        self.log("web server logs are:")
289
267
        http_logs = self.get_readonly_server().logs
290
268
        self.log('\n'.join(http_logs))
291
 
        # unfortunately this log entry is branch format specific. We could
292
 
        # factor out the 'what files does this format use' to a method on the
 
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 
293
271
        # repository, which would let us to this generically. RBC 20060419
294
 
        # RBC 20080408: Or perhaps we can assert that no files are fully read
295
 
        # twice?
296
272
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
297
273
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
298
274
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
299
 
        # this r-h check test will prevent regressions, but it currently already
 
275
        # this r-h check test will prevent regressions, but it currently already 
300
276
        # passes, before the patch to cache-rh is applied :[
301
277
        self.assertTrue(1 >= self._count_log_matches('revision-history',
302
278
                                                     http_logs))
304
280
                                                     http_logs))
305
281
        # FIXME naughty poking in there.
306
282
        self.get_readonly_server().logs = []
307
 
        # check there is nothing more to fetch.  We take care to re-use the
308
 
        # existing transport so that the request logs we're about to examine
309
 
        # aren't cluttered with redundant probes for a smart server.
310
 
        # XXX: Perhaps this further parameterisation: test http with smart
311
 
        # server, and test http without smart server?
312
 
        source = Branch.open(
313
 
            self.get_readonly_url("source/"),
314
 
            possible_transports=[source.bzrdir.root_transport])
315
 
        target.fetch(source)
 
283
        # check there is nothing more to fetch
 
284
        source = Branch.open(self.get_readonly_url("source/"))
 
285
        self.assertEqual(target.fetch(source), (0, []))
316
286
        # should make just two requests
317
287
        http_logs = self.get_readonly_server().logs
318
288
        self.log("web server logs are:")
319
289
        self.log('\n'.join(http_logs))
320
290
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
321
291
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
322
 
        self.assertEqual(1, self._count_log_matches('repository/format',
323
 
            http_logs))
 
292
        self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
324
293
        self.assertTrue(1 >= self._count_log_matches('revision-history',
325
294
                                                     http_logs))
326
295
        self.assertTrue(1 >= self._count_log_matches('last-revision',
327
296
                                                     http_logs))
328
297
        self.assertEqual(4, len(http_logs))
329
 
 
330
 
 
331
 
class TestKnitToPackFetch(TestCaseWithTransport):
332
 
 
333
 
    def find_get_record_stream(self, calls, expected_count=1):
334
 
        """In a list of calls, find the last 'get_record_stream'.
335
 
 
336
 
        :param expected_count: The number of calls we should exepect to find.
337
 
            If a different number is found, an assertion is raised.
338
 
        """
339
 
        get_record_call = None
340
 
        call_count = 0
341
 
        for call in calls:
342
 
            if call[0] == 'get_record_stream':
343
 
                call_count += 1
344
 
                get_record_call = call
345
 
        self.assertEqual(expected_count, call_count)
346
 
        return get_record_call
347
 
 
348
 
    def test_fetch_with_deltas_no_delta_closure(self):
349
 
        tree = self.make_branch_and_tree('source', format='dirstate')
350
 
        target = self.make_repository('target', format='pack-0.92')
351
 
        self.build_tree(['source/file'])
352
 
        tree.set_root_id('root-id')
353
 
        tree.add('file', 'file-id')
354
 
        tree.commit('one', rev_id='rev-one')
355
 
        source = tree.branch.repository
356
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
357
 
                        source.texts)
358
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
359
 
                        source.signatures)
360
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
361
 
                        source.revisions)
362
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
363
 
                        source.inventories)
364
 
        # precondition
365
 
        self.assertTrue(target._format._fetch_uses_deltas)
366
 
        target.fetch(source, revision_id='rev-one')
367
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
368
 
                          target._format._fetch_order, False),
369
 
                         self.find_get_record_stream(source.texts.calls))
370
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
371
 
          target._format._fetch_order, False),
372
 
          self.find_get_record_stream(source.inventories.calls, 2))
373
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
374
 
                          target._format._fetch_order, False),
375
 
                         self.find_get_record_stream(source.revisions.calls))
376
 
        # XXX: Signatures is special, and slightly broken. The
377
 
        # standard item_keys_introduced_by actually does a lookup for every
378
 
        # signature to see if it exists, rather than waiting to do them all at
379
 
        # once at the end. The fetch code then does an all-at-once and just
380
 
        # allows for some of them to be missing.
381
 
        # So we know there will be extra calls, but the *last* one is the one
382
 
        # we care about.
383
 
        signature_calls = source.signatures.calls[-1:]
384
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
385
 
                          target._format._fetch_order, False),
386
 
                         self.find_get_record_stream(signature_calls))
387
 
 
388
 
    def test_fetch_no_deltas_with_delta_closure(self):
389
 
        tree = self.make_branch_and_tree('source', format='dirstate')
390
 
        target = self.make_repository('target', format='pack-0.92')
391
 
        self.build_tree(['source/file'])
392
 
        tree.set_root_id('root-id')
393
 
        tree.add('file', 'file-id')
394
 
        tree.commit('one', rev_id='rev-one')
395
 
        source = tree.branch.repository
396
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
397
 
                        source.texts)
398
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
399
 
                        source.signatures)
400
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
401
 
                        source.revisions)
402
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
403
 
                        source.inventories)
404
 
        # XXX: This won't work in general, but for the dirstate format it does.
405
 
        old_fetch_uses_deltas_setting = target._format._fetch_uses_deltas
406
 
        def restore():
407
 
            target._format._fetch_uses_deltas = old_fetch_uses_deltas_setting
408
 
        self.addCleanup(restore)
409
 
        target._format._fetch_uses_deltas = False
410
 
        target.fetch(source, revision_id='rev-one')
411
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
412
 
                          target._format._fetch_order, True),
413
 
                         self.find_get_record_stream(source.texts.calls))
414
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
415
 
            target._format._fetch_order, True),
416
 
            self.find_get_record_stream(source.inventories.calls, 2))
417
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
418
 
                          target._format._fetch_order, True),
419
 
                         self.find_get_record_stream(source.revisions.calls))
420
 
        # XXX: Signatures is special, and slightly broken. The
421
 
        # standard item_keys_introduced_by actually does a lookup for every
422
 
        # signature to see if it exists, rather than waiting to do them all at
423
 
        # once at the end. The fetch code then does an all-at-once and just
424
 
        # allows for some of them to be missing.
425
 
        # So we know there will be extra calls, but the *last* one is the one
426
 
        # we care about.
427
 
        signature_calls = source.signatures.calls[-1:]
428
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
429
 
                          target._format._fetch_order, True),
430
 
                         self.find_get_record_stream(signature_calls))
431
 
 
432
 
    def test_fetch_revisions_with_deltas_into_pack(self):
433
 
        # See BUG #261339, dev versions of bzr could accidentally create deltas
434
 
        # in revision texts in knit branches (when fetching from packs). So we
435
 
        # ensure that *if* a knit repository has a delta in revisions, that it
436
 
        # gets properly expanded back into a fulltext when stored in the pack
437
 
        # file.
438
 
        tree = self.make_branch_and_tree('source', format='dirstate')
439
 
        target = self.make_repository('target', format='pack-0.92')
440
 
        self.build_tree(['source/file'])
441
 
        tree.set_root_id('root-id')
442
 
        tree.add('file', 'file-id')
443
 
        tree.commit('one', rev_id='rev-one')
444
 
        # Hack the KVF for revisions so that it "accidentally" allows a delta
445
 
        tree.branch.repository.revisions._max_delta_chain = 200
446
 
        tree.commit('two', rev_id='rev-two')
447
 
        source = tree.branch.repository
448
 
        # Ensure that we stored a delta
449
 
        source.lock_read()
450
 
        self.addCleanup(source.unlock)
451
 
        record = source.revisions.get_record_stream([('rev-two',)],
452
 
            'unordered', False).next()
453
 
        self.assertEqual('knit-delta-gz', record.storage_kind)
454
 
        target.fetch(tree.branch.repository, revision_id='rev-two')
455
 
        # The record should get expanded back to a fulltext
456
 
        target.lock_read()
457
 
        self.addCleanup(target.unlock)
458
 
        record = target.revisions.get_record_stream([('rev-two',)],
459
 
            'unordered', False).next()
460
 
        self.assertEqual('knit-ft-gz', record.storage_kind)
461
 
 
462
 
    def test_fetch_with_fallback_and_merge(self):
463
 
        builder = self.make_branch_builder('source', format='pack-0.92')
464
 
        builder.start_series()
465
 
        # graph
466
 
        #   A
467
 
        #   |\
468
 
        #   B C
469
 
        #   | |
470
 
        #   | D
471
 
        #   | |
472
 
        #   | E
473
 
        #    \|
474
 
        #     F
475
 
        # A & B are present in the base (stacked-on) repository, A-E are
476
 
        # present in the source.
477
 
        # This reproduces bug #304841
478
 
        # We need a large enough inventory that total size of compressed deltas
479
 
        # is shorter than the size of a compressed fulltext. We have to use
480
 
        # random ids because otherwise the inventory fulltext compresses too
481
 
        # well and the deltas get bigger.
482
 
        to_add = [
483
 
            ('add', ('', 'TREE_ROOT', 'directory', None))]
484
 
        for i in xrange(10):
485
 
            fname = 'file%03d' % (i,)
486
 
            fileid = '%s-%s' % (fname, osutils.rand_chars(64))
487
 
            to_add.append(('add', (fname, fileid, 'file', 'content\n')))
488
 
        builder.build_snapshot('A', None, to_add)
489
 
        builder.build_snapshot('B', ['A'], [])
490
 
        builder.build_snapshot('C', ['A'], [])
491
 
        builder.build_snapshot('D', ['C'], [])
492
 
        builder.build_snapshot('E', ['D'], [])
493
 
        builder.build_snapshot('F', ['E', 'B'], [])
494
 
        builder.finish_series()
495
 
        source_branch = builder.get_branch()
496
 
        source_branch.bzrdir.sprout('base', revision_id='B')
497
 
        target_branch = self.make_branch('target', format='1.6')
498
 
        target_branch.set_stacked_on_url('../base')
499
 
        source = source_branch.repository
500
 
        source.lock_read()
501
 
        self.addCleanup(source.unlock)
502
 
        source.inventories = versionedfile.OrderingVersionedFilesDecorator(
503
 
                        source.inventories,
504
 
                        key_priority={('E',): 1, ('D',): 2, ('C',): 4,
505
 
                                      ('F',): 3})
506
 
        # Ensure that the content is yielded in the proper order, and given as
507
 
        # the expected kinds
508
 
        records = [(record.key, record.storage_kind)
509
 
                   for record in source.inventories.get_record_stream(
510
 
                        [('D',), ('C',), ('E',), ('F',)], 'unordered', False)]
511
 
        self.assertEqual([(('E',), 'knit-delta-gz'), (('D',), 'knit-delta-gz'),
512
 
                          (('F',), 'knit-delta-gz'), (('C',), 'knit-delta-gz')],
513
 
                          records)
514
 
 
515
 
        target_branch.lock_write()
516
 
        self.addCleanup(target_branch.unlock)
517
 
        target = target_branch.repository
518
 
        target.fetch(source, revision_id='F')
519
 
        # 'C' should be expanded to a fulltext, but D and E should still be
520
 
        # deltas
521
 
        stream = target.inventories.get_record_stream(
522
 
            [('C',), ('D',), ('E',), ('F',)],
523
 
            'unordered', False)
524
 
        kinds = dict((record.key, record.storage_kind) for record in stream)
525
 
        self.assertEqual({('C',): 'knit-ft-gz', ('D',): 'knit-delta-gz',
526
 
                          ('E',): 'knit-delta-gz', ('F',): 'knit-delta-gz'},
527
 
                         kinds)
528
 
 
529
 
 
530
 
class Test1To2Fetch(TestCaseWithTransport):
531
 
    """Tests for Model1To2 failure modes"""
532
 
 
533
 
    def make_tree_and_repo(self):
534
 
        self.tree = self.make_branch_and_tree('tree', format='pack-0.92')
535
 
        self.repo = self.make_repository('rich-repo', format='rich-root-pack')
536
 
        self.repo.lock_write()
537
 
        self.addCleanup(self.repo.unlock)
538
 
 
539
 
    def do_fetch_order_test(self, first, second):
540
 
        """Test that fetch works no matter what the set order of revision is.
541
 
 
542
 
        This test depends on the order of items in a set, which is
543
 
        implementation-dependant, so we test A, B and then B, A.
544
 
        """
545
 
        self.make_tree_and_repo()
546
 
        self.tree.commit('Commit 1', rev_id=first)
547
 
        self.tree.commit('Commit 2', rev_id=second)
548
 
        self.repo.fetch(self.tree.branch.repository, second)
549
 
 
550
 
    def test_fetch_order_AB(self):
551
 
        """See do_fetch_order_test"""
552
 
        self.do_fetch_order_test('A', 'B')
553
 
 
554
 
    def test_fetch_order_BA(self):
555
 
        """See do_fetch_order_test"""
556
 
        self.do_fetch_order_test('B', 'A')
557
 
 
558
 
    def get_parents(self, file_id, revision_id):
559
 
        self.repo.lock_read()
560
 
        try:
561
 
            parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
562
 
            return parent_map[(file_id, revision_id)]
563
 
        finally:
564
 
            self.repo.unlock()
565
 
 
566
 
    def test_fetch_ghosts(self):
567
 
        self.make_tree_and_repo()
568
 
        self.tree.commit('first commit', rev_id='left-parent')
569
 
        self.tree.add_parent_tree_id('ghost-parent')
570
 
        fork = self.tree.bzrdir.sprout('fork', 'null:').open_workingtree()
571
 
        fork.commit('not a ghost', rev_id='not-ghost-parent')
572
 
        self.tree.branch.repository.fetch(fork.branch.repository,
573
 
                                     'not-ghost-parent')
574
 
        self.tree.add_parent_tree_id('not-ghost-parent')
575
 
        self.tree.commit('second commit', rev_id='second-id')
576
 
        self.repo.fetch(self.tree.branch.repository, 'second-id')
577
 
        root_id = self.tree.get_root_id()
578
 
        self.assertEqual(
579
 
            ((root_id, 'left-parent'), (root_id, 'ghost-parent'),
580
 
             (root_id, 'not-ghost-parent')),
581
 
            self.get_parents(root_id, 'second-id'))
582
 
 
583
 
    def make_two_commits(self, change_root, fetch_twice):
584
 
        self.make_tree_and_repo()
585
 
        self.tree.commit('first commit', rev_id='first-id')
586
 
        if change_root:
587
 
            self.tree.set_root_id('unique-id')
588
 
        self.tree.commit('second commit', rev_id='second-id')
589
 
        if fetch_twice:
590
 
            self.repo.fetch(self.tree.branch.repository, 'first-id')
591
 
        self.repo.fetch(self.tree.branch.repository, 'second-id')
592
 
 
593
 
    def test_fetch_changed_root(self):
594
 
        self.make_two_commits(change_root=True, fetch_twice=False)
595
 
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
596
 
 
597
 
    def test_two_fetch_changed_root(self):
598
 
        self.make_two_commits(change_root=True, fetch_twice=True)
599
 
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
600
 
 
601
 
    def test_two_fetches(self):
602
 
        self.make_two_commits(change_root=False, fetch_twice=True)
603
 
        self.assertEqual((('TREE_ROOT', 'first-id'),),
604
 
            self.get_parents('TREE_ROOT', 'second-id'))