/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-18 18:18:36 UTC
  • mto: This revision was merged to the branch mainline in revision 4461.
  • Revision ID: john@arbash-meinel.com-20090618181836-biodfkat9a8eyzjz
The new add_inventory_by_delta is returning a CHKInventory when mapping from NULL
Which is completely valid, but 'broke' one of the tests.
So to fix it, changed the test to use CHKInventories on both sides, and add an __eq__
member. The nice thing is that CHKInventory.__eq__ is fairly cheap, since it only
has to check the root keys.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2007 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import os
 
18
import re
 
19
import sys
 
20
 
 
21
import bzrlib
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    errors,
 
25
    osutils,
 
26
    merge,
 
27
    repository,
 
28
    versionedfile,
 
29
    )
 
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.http_utils import TestCaseWithWebserver
 
35
from bzrlib.tests.test_revision import make_branches
 
36
from bzrlib.trace import mutter
 
37
from bzrlib.upgrade import Convert
 
38
from bzrlib.workingtree import WorkingTree
 
39
 
 
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
 
 
45
def has_revision(branch, revision_id):
 
46
    return branch.repository.has_revision(revision_id)
 
47
 
 
48
def fetch_steps(self, br_a, br_b, writable_a):
 
49
    """A foreign test method for testing fetch locally and remotely."""
 
50
 
 
51
    # TODO RBC 20060201 make this a repository test.
 
52
    repo_b = br_b.repository
 
53
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
 
54
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
 
55
    self.assertEquals(len(br_b.revision_history()), 7)
 
56
    br_b.fetch(br_a, br_a.revision_history()[2])
 
57
    # branch.fetch is not supposed to alter the revision history
 
58
    self.assertEquals(len(br_b.revision_history()), 7)
 
59
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
 
60
 
 
61
    # fetching the next revision up in sample data copies one revision
 
62
    br_b.fetch(br_a, br_a.revision_history()[3])
 
63
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
 
64
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
 
65
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
 
66
 
 
67
    # When a non-branch ancestor is missing, it should be unlisted...
 
68
    # as its not reference from the inventory weave.
 
69
    br_b4 = self.make_branch('br_4')
 
70
    br_b4.fetch(br_b)
 
71
 
 
72
    writable_a.fetch(br_b)
 
73
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
 
74
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
 
75
 
 
76
    br_b2 = self.make_branch('br_b2')
 
77
    br_b2.fetch(br_b)
 
78
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
 
79
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
 
80
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
 
81
 
 
82
    br_a2 = self.make_branch('br_a2')
 
83
    br_a2.fetch(br_a)
 
84
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
 
85
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
 
86
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
 
87
 
 
88
    br_a3 = self.make_branch('br_a3')
 
89
    # pulling a branch with no revisions grabs nothing, regardless of
 
90
    # whats in the inventory.
 
91
    br_a3.fetch(br_a2)
 
92
    for revno in range(4):
 
93
        self.assertFalse(
 
94
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
 
95
    br_a3.fetch(br_a2, br_a.revision_history()[2])
 
96
    # pull the 3 revisions introduced by a@u-0-3
 
97
    br_a3.fetch(br_a2, br_a.revision_history()[3])
 
98
    # NoSuchRevision should be raised if the branch is missing the revision
 
99
    # that was requested.
 
100
    self.assertRaises(errors.NoSuchRevision, br_a3.fetch, br_a2, 'pizza')
 
101
 
 
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
 
107
 
 
108
    #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
 
110
    # add the right parents.
 
111
 
 
112
 
 
113
class TestFetch(TestCaseWithTransport):
 
114
 
 
115
    def test_fetch(self):
 
116
        #highest indices a: 5, b: 7
 
117
        br_a, br_b = make_branches(self, format='dirstate-tags')
 
118
        fetch_steps(self, br_a, br_b, br_a)
 
119
 
 
120
    def test_fetch_self(self):
 
121
        wt = self.make_branch_and_tree('br')
 
122
        wt.branch.fetch(wt.branch)
 
123
 
 
124
    def test_fetch_root_knit(self):
 
125
        """Ensure that knit2.fetch() updates the root knit
 
126
 
 
127
        This tests the case where the root has a new revision, but there are no
 
128
        corresponding filename, parent, contents or other changes.
 
129
        """
 
130
        knit1_format = bzrdir.BzrDirMetaFormat1()
 
131
        knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
 
132
        knit2_format = bzrdir.BzrDirMetaFormat1()
 
133
        knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
 
134
        # we start with a knit1 repository because that causes the
 
135
        # root revision to change for each commit, even though the content,
 
136
        # parent, name, and other attributes are unchanged.
 
137
        tree = self.make_branch_and_tree('tree', knit1_format)
 
138
        tree.set_root_id('tree-root')
 
139
        tree.commit('rev1', rev_id='rev1')
 
140
        tree.commit('rev2', rev_id='rev2')
 
141
 
 
142
        # Now we convert it to a knit2 repository so that it has a root knit
 
143
        Convert(tree.basedir, knit2_format)
 
144
        tree = WorkingTree.open(tree.basedir)
 
145
        branch = self.make_branch('branch', format=knit2_format)
 
146
        branch.pull(tree.branch, stop_revision='rev1')
 
147
        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()
 
156
        branch.pull(tree.branch)
 
157
        # Make sure that the next revision in the root knit was retrieved,
 
158
        # 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")
 
177
 
 
178
 
 
179
class TestMergeFetch(TestCaseWithTransport):
 
180
 
 
181
    def test_merge_fetches_unrelated(self):
 
182
        """Merge brings across history from unrelated source"""
 
183
        wt1 = self.make_branch_and_tree('br1')
 
184
        br1 = wt1.branch
 
185
        wt1.commit(message='rev 1-1', rev_id='1-1')
 
186
        wt1.commit(message='rev 1-2', rev_id='1-2')
 
187
        wt2 = self.make_branch_and_tree('br2')
 
188
        br2 = wt2.branch
 
189
        wt2.commit(message='rev 2-1', rev_id='2-1')
 
190
        wt2.merge_from_branch(br1, from_revision='null:')
 
191
        self._check_revs_present(br2)
 
192
 
 
193
    def test_merge_fetches(self):
 
194
        """Merge brings across history from source"""
 
195
        wt1 = self.make_branch_and_tree('br1')
 
196
        br1 = wt1.branch
 
197
        wt1.commit(message='rev 1-1', rev_id='1-1')
 
198
        dir_2 = br1.bzrdir.sprout('br2')
 
199
        br2 = dir_2.open_branch()
 
200
        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)
 
204
        self._check_revs_present(br2)
 
205
 
 
206
    def _check_revs_present(self, br2):
 
207
        for rev_id in '1-1', '1-2', '2-1':
 
208
            self.assertTrue(br2.repository.has_revision(rev_id))
 
209
            rev = br2.repository.get_revision(rev_id)
 
210
            self.assertEqual(rev.revision_id, rev_id)
 
211
            self.assertTrue(br2.repository.get_inventory(rev_id))
 
212
 
 
213
 
 
214
class TestMergeFileHistory(TestCaseWithTransport):
 
215
 
 
216
    def setUp(self):
 
217
        super(TestMergeFileHistory, self).setUp()
 
218
        wt1 = self.make_branch_and_tree('br1')
 
219
        br1 = wt1.branch
 
220
        self.build_tree_contents([('br1/file', 'original contents\n')])
 
221
        wt1.add('file', 'this-file-id')
 
222
        wt1.commit(message='rev 1-1', rev_id='1-1')
 
223
        dir_2 = br1.bzrdir.sprout('br2')
 
224
        br2 = dir_2.open_branch()
 
225
        wt2 = dir_2.open_workingtree()
 
226
        self.build_tree_contents([('br1/file', 'original from 1\n')])
 
227
        wt1.commit(message='rev 1-2', rev_id='1-2')
 
228
        self.build_tree_contents([('br1/file', 'agreement\n')])
 
229
        wt1.commit(message='rev 1-3', rev_id='1-3')
 
230
        self.build_tree_contents([('br2/file', 'contents in 2\n')])
 
231
        wt2.commit(message='rev 2-1', rev_id='2-1')
 
232
        self.build_tree_contents([('br2/file', 'agreement\n')])
 
233
        wt2.commit(message='rev 2-2', rev_id='2-2')
 
234
 
 
235
    def test_merge_fetches_file_history(self):
 
236
        """Merge brings across file histories"""
 
237
        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)
 
242
        for rev_id, text in [('1-2', 'original from 1\n'),
 
243
                             ('1-3', 'agreement\n'),
 
244
                             ('2-1', 'contents in 2\n'),
 
245
                             ('2-2', 'agreement\n')]:
 
246
            self.assertEqualDiff(
 
247
                br2.repository.revision_tree(
 
248
                    rev_id).get_file_text('this-file-id'), text)
 
249
 
 
250
 
 
251
class TestHttpFetch(TestCaseWithWebserver):
 
252
    # FIXME RBC 20060124 this really isn't web specific, perhaps an
 
253
    # instrumented readonly transport? Can we do an instrumented
 
254
    # adapter and use self.get_readonly_url ?
 
255
 
 
256
    def test_fetch(self):
 
257
        #highest indices a: 5, b: 7
 
258
        br_a, br_b = make_branches(self)
 
259
        br_rem_a = Branch.open(self.get_readonly_url('branch1'))
 
260
        fetch_steps(self, br_rem_a, br_b, br_a)
 
261
 
 
262
    def _count_log_matches(self, target, logs):
 
263
        """Count the number of times the target file pattern was fetched in an http log"""
 
264
        get_succeeds_re = re.compile(
 
265
            '.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
 
266
            (     target,                    bzrlib.__version__))
 
267
        c = 0
 
268
        for line in logs:
 
269
            if get_succeeds_re.match(line):
 
270
                c += 1
 
271
        return c
 
272
 
 
273
    def test_weaves_are_retrieved_once(self):
 
274
        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')
 
277
        branch = wt.branch
 
278
        wt.add(["file"], ["id"])
 
279
        wt.commit("added file")
 
280
        open("source/file", 'w').write("blah\n")
 
281
        wt.commit("changed file")
 
282
        target = BzrDir.create_branch_and_repo("target/")
 
283
        source = Branch.open(self.get_readonly_url("source/"))
 
284
        target.fetch(source)
 
285
        # this is the path to the literal file. As format changes
 
286
        # occur it needs to be updated. FIXME: ask the store for the
 
287
        # path.
 
288
        self.log("web server logs are:")
 
289
        http_logs = self.get_readonly_server().logs
 
290
        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
 
293
        # 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
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
 
297
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
 
298
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
 
299
        # this r-h check test will prevent regressions, but it currently already
 
300
        # passes, before the patch to cache-rh is applied :[
 
301
        self.assertTrue(1 >= self._count_log_matches('revision-history',
 
302
                                                     http_logs))
 
303
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
304
                                                     http_logs))
 
305
        # FIXME naughty poking in there.
 
306
        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)
 
316
        # should make just two requests
 
317
        http_logs = self.get_readonly_server().logs
 
318
        self.log("web server logs are:")
 
319
        self.log('\n'.join(http_logs))
 
320
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
 
321
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
 
322
        self.assertEqual(1, self._count_log_matches('repository/format',
 
323
            http_logs))
 
324
        self.assertEqual(1, self._count_log_matches('revisions.kndx',
 
325
            http_logs))
 
326
        self.assertTrue(1 >= self._count_log_matches('revision-history',
 
327
                                                     http_logs))
 
328
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
329
                                                     http_logs))
 
330
        self.assertLength(5, http_logs)
 
331
 
 
332
 
 
333
class TestKnitToPackFetch(TestCaseWithTransport):
 
334
 
 
335
    def find_get_record_stream(self, calls, expected_count=1):
 
336
        """In a list of calls, find the last 'get_record_stream'.
 
337
 
 
338
        :param expected_count: The number of calls we should exepect to find.
 
339
            If a different number is found, an assertion is raised.
 
340
        """
 
341
        get_record_call = None
 
342
        call_count = 0
 
343
        for call in calls:
 
344
            if call[0] == 'get_record_stream':
 
345
                call_count += 1
 
346
                get_record_call = call
 
347
        self.assertEqual(expected_count, call_count)
 
348
        return get_record_call
 
349
 
 
350
    def test_fetch_with_deltas_no_delta_closure(self):
 
351
        tree = self.make_branch_and_tree('source', format='dirstate')
 
352
        target = self.make_repository('target', format='pack-0.92')
 
353
        self.build_tree(['source/file'])
 
354
        tree.set_root_id('root-id')
 
355
        tree.add('file', 'file-id')
 
356
        tree.commit('one', rev_id='rev-one')
 
357
        source = tree.branch.repository
 
358
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
 
359
                        source.texts)
 
360
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
 
361
                        source.signatures)
 
362
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
 
363
                        source.revisions)
 
364
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
 
365
                        source.inventories)
 
366
        # precondition
 
367
        self.assertTrue(target._format._fetch_uses_deltas)
 
368
        target.fetch(source, revision_id='rev-one')
 
369
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
 
370
                          target._format._fetch_order, False),
 
371
                         self.find_get_record_stream(source.texts.calls))
 
372
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
373
          target._format._fetch_order, False),
 
374
          self.find_get_record_stream(source.inventories.calls, 2))
 
375
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
376
                          target._format._fetch_order, False),
 
377
                         self.find_get_record_stream(source.revisions.calls))
 
378
        # XXX: Signatures is special, and slightly broken. The
 
379
        # standard item_keys_introduced_by actually does a lookup for every
 
380
        # signature to see if it exists, rather than waiting to do them all at
 
381
        # once at the end. The fetch code then does an all-at-once and just
 
382
        # allows for some of them to be missing.
 
383
        # So we know there will be extra calls, but the *last* one is the one
 
384
        # we care about.
 
385
        signature_calls = source.signatures.calls[-1:]
 
386
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
387
                          target._format._fetch_order, False),
 
388
                         self.find_get_record_stream(signature_calls))
 
389
 
 
390
    def test_fetch_no_deltas_with_delta_closure(self):
 
391
        tree = self.make_branch_and_tree('source', format='dirstate')
 
392
        target = self.make_repository('target', format='pack-0.92')
 
393
        self.build_tree(['source/file'])
 
394
        tree.set_root_id('root-id')
 
395
        tree.add('file', 'file-id')
 
396
        tree.commit('one', rev_id='rev-one')
 
397
        source = tree.branch.repository
 
398
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
 
399
                        source.texts)
 
400
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
 
401
                        source.signatures)
 
402
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
 
403
                        source.revisions)
 
404
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
 
405
                        source.inventories)
 
406
        # XXX: This won't work in general, but for the dirstate format it does.
 
407
        old_fetch_uses_deltas_setting = target._format._fetch_uses_deltas
 
408
        def restore():
 
409
            target._format._fetch_uses_deltas = old_fetch_uses_deltas_setting
 
410
        self.addCleanup(restore)
 
411
        target._format._fetch_uses_deltas = False
 
412
        target.fetch(source, revision_id='rev-one')
 
413
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
 
414
                          target._format._fetch_order, True),
 
415
                         self.find_get_record_stream(source.texts.calls))
 
416
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
417
            target._format._fetch_order, True),
 
418
            self.find_get_record_stream(source.inventories.calls, 2))
 
419
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
420
                          target._format._fetch_order, True),
 
421
                         self.find_get_record_stream(source.revisions.calls))
 
422
        # XXX: Signatures is special, and slightly broken. The
 
423
        # standard item_keys_introduced_by actually does a lookup for every
 
424
        # signature to see if it exists, rather than waiting to do them all at
 
425
        # once at the end. The fetch code then does an all-at-once and just
 
426
        # allows for some of them to be missing.
 
427
        # So we know there will be extra calls, but the *last* one is the one
 
428
        # we care about.
 
429
        signature_calls = source.signatures.calls[-1:]
 
430
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
431
                          target._format._fetch_order, True),
 
432
                         self.find_get_record_stream(signature_calls))
 
433
 
 
434
    def test_fetch_revisions_with_deltas_into_pack(self):
 
435
        # See BUG #261339, dev versions of bzr could accidentally create deltas
 
436
        # in revision texts in knit branches (when fetching from packs). So we
 
437
        # ensure that *if* a knit repository has a delta in revisions, that it
 
438
        # gets properly expanded back into a fulltext when stored in the pack
 
439
        # file.
 
440
        tree = self.make_branch_and_tree('source', format='dirstate')
 
441
        target = self.make_repository('target', format='pack-0.92')
 
442
        self.build_tree(['source/file'])
 
443
        tree.set_root_id('root-id')
 
444
        tree.add('file', 'file-id')
 
445
        tree.commit('one', rev_id='rev-one')
 
446
        # Hack the KVF for revisions so that it "accidentally" allows a delta
 
447
        tree.branch.repository.revisions._max_delta_chain = 200
 
448
        tree.commit('two', rev_id='rev-two')
 
449
        source = tree.branch.repository
 
450
        # Ensure that we stored a delta
 
451
        source.lock_read()
 
452
        self.addCleanup(source.unlock)
 
453
        record = source.revisions.get_record_stream([('rev-two',)],
 
454
            'unordered', False).next()
 
455
        self.assertEqual('knit-delta-gz', record.storage_kind)
 
456
        target.fetch(tree.branch.repository, revision_id='rev-two')
 
457
        # The record should get expanded back to a fulltext
 
458
        target.lock_read()
 
459
        self.addCleanup(target.unlock)
 
460
        record = target.revisions.get_record_stream([('rev-two',)],
 
461
            'unordered', False).next()
 
462
        self.assertEqual('knit-ft-gz', record.storage_kind)
 
463
 
 
464
    def test_fetch_with_fallback_and_merge(self):
 
465
        builder = self.make_branch_builder('source', format='pack-0.92')
 
466
        builder.start_series()
 
467
        # graph
 
468
        #   A
 
469
        #   |\
 
470
        #   B C
 
471
        #   | |
 
472
        #   | D
 
473
        #   | |
 
474
        #   | E
 
475
        #    \|
 
476
        #     F
 
477
        # A & B are present in the base (stacked-on) repository, A-E are
 
478
        # present in the source.
 
479
        # This reproduces bug #304841
 
480
        # We need a large enough inventory that total size of compressed deltas
 
481
        # is shorter than the size of a compressed fulltext. We have to use
 
482
        # random ids because otherwise the inventory fulltext compresses too
 
483
        # well and the deltas get bigger.
 
484
        to_add = [
 
485
            ('add', ('', 'TREE_ROOT', 'directory', None))]
 
486
        for i in xrange(10):
 
487
            fname = 'file%03d' % (i,)
 
488
            fileid = '%s-%s' % (fname, osutils.rand_chars(64))
 
489
            to_add.append(('add', (fname, fileid, 'file', 'content\n')))
 
490
        builder.build_snapshot('A', None, to_add)
 
491
        builder.build_snapshot('B', ['A'], [])
 
492
        builder.build_snapshot('C', ['A'], [])
 
493
        builder.build_snapshot('D', ['C'], [])
 
494
        builder.build_snapshot('E', ['D'], [])
 
495
        builder.build_snapshot('F', ['E', 'B'], [])
 
496
        builder.finish_series()
 
497
        source_branch = builder.get_branch()
 
498
        source_branch.bzrdir.sprout('base', revision_id='B')
 
499
        target_branch = self.make_branch('target', format='1.6')
 
500
        target_branch.set_stacked_on_url('../base')
 
501
        source = source_branch.repository
 
502
        source.lock_read()
 
503
        self.addCleanup(source.unlock)
 
504
        source.inventories = versionedfile.OrderingVersionedFilesDecorator(
 
505
                        source.inventories,
 
506
                        key_priority={('E',): 1, ('D',): 2, ('C',): 4,
 
507
                                      ('F',): 3})
 
508
        # Ensure that the content is yielded in the proper order, and given as
 
509
        # the expected kinds
 
510
        records = [(record.key, record.storage_kind)
 
511
                   for record in source.inventories.get_record_stream(
 
512
                        [('D',), ('C',), ('E',), ('F',)], 'unordered', False)]
 
513
        self.assertEqual([(('E',), 'knit-delta-gz'), (('D',), 'knit-delta-gz'),
 
514
                          (('F',), 'knit-delta-gz'), (('C',), 'knit-delta-gz')],
 
515
                          records)
 
516
 
 
517
        target_branch.lock_write()
 
518
        self.addCleanup(target_branch.unlock)
 
519
        target = target_branch.repository
 
520
        target.fetch(source, revision_id='F')
 
521
        # 'C' should be expanded to a fulltext, but D and E should still be
 
522
        # deltas
 
523
        stream = target.inventories.get_record_stream(
 
524
            [('C',), ('D',), ('E',), ('F',)],
 
525
            'unordered', False)
 
526
        kinds = dict((record.key, record.storage_kind) for record in stream)
 
527
        self.assertEqual({('C',): 'knit-ft-gz', ('D',): 'knit-delta-gz',
 
528
                          ('E',): 'knit-delta-gz', ('F',): 'knit-delta-gz'},
 
529
                         kinds)
 
530
 
 
531
 
 
532
class Test1To2Fetch(TestCaseWithTransport):
 
533
    """Tests for Model1To2 failure modes"""
 
534
 
 
535
    def make_tree_and_repo(self):
 
536
        self.tree = self.make_branch_and_tree('tree', format='pack-0.92')
 
537
        self.repo = self.make_repository('rich-repo', format='rich-root-pack')
 
538
        self.repo.lock_write()
 
539
        self.addCleanup(self.repo.unlock)
 
540
 
 
541
    def do_fetch_order_test(self, first, second):
 
542
        """Test that fetch works no matter what the set order of revision is.
 
543
 
 
544
        This test depends on the order of items in a set, which is
 
545
        implementation-dependant, so we test A, B and then B, A.
 
546
        """
 
547
        self.make_tree_and_repo()
 
548
        self.tree.commit('Commit 1', rev_id=first)
 
549
        self.tree.commit('Commit 2', rev_id=second)
 
550
        self.repo.fetch(self.tree.branch.repository, second)
 
551
 
 
552
    def test_fetch_order_AB(self):
 
553
        """See do_fetch_order_test"""
 
554
        self.do_fetch_order_test('A', 'B')
 
555
 
 
556
    def test_fetch_order_BA(self):
 
557
        """See do_fetch_order_test"""
 
558
        self.do_fetch_order_test('B', 'A')
 
559
 
 
560
    def get_parents(self, file_id, revision_id):
 
561
        self.repo.lock_read()
 
562
        try:
 
563
            parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
 
564
            return parent_map[(file_id, revision_id)]
 
565
        finally:
 
566
            self.repo.unlock()
 
567
 
 
568
    def test_fetch_ghosts(self):
 
569
        self.make_tree_and_repo()
 
570
        self.tree.commit('first commit', rev_id='left-parent')
 
571
        self.tree.add_parent_tree_id('ghost-parent')
 
572
        fork = self.tree.bzrdir.sprout('fork', 'null:').open_workingtree()
 
573
        fork.commit('not a ghost', rev_id='not-ghost-parent')
 
574
        self.tree.branch.repository.fetch(fork.branch.repository,
 
575
                                     'not-ghost-parent')
 
576
        self.tree.add_parent_tree_id('not-ghost-parent')
 
577
        self.tree.commit('second commit', rev_id='second-id')
 
578
        self.repo.fetch(self.tree.branch.repository, 'second-id')
 
579
        root_id = self.tree.get_root_id()
 
580
        self.assertEqual(
 
581
            ((root_id, 'left-parent'), (root_id, 'not-ghost-parent')),
 
582
            self.get_parents(root_id, 'second-id'))
 
583
 
 
584
    def make_two_commits(self, change_root, fetch_twice):
 
585
        self.make_tree_and_repo()
 
586
        self.tree.commit('first commit', rev_id='first-id')
 
587
        if change_root:
 
588
            self.tree.set_root_id('unique-id')
 
589
        self.tree.commit('second commit', rev_id='second-id')
 
590
        if fetch_twice:
 
591
            self.repo.fetch(self.tree.branch.repository, 'first-id')
 
592
        self.repo.fetch(self.tree.branch.repository, 'second-id')
 
593
 
 
594
    def test_fetch_changed_root(self):
 
595
        self.make_two_commits(change_root=True, fetch_twice=False)
 
596
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
 
597
 
 
598
    def test_two_fetch_changed_root(self):
 
599
        self.make_two_commits(change_root=True, fetch_twice=True)
 
600
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
 
601
 
 
602
    def test_two_fetches(self):
 
603
        self.make_two_commits(change_root=False, fetch_twice=True)
 
604
        self.assertEqual((('TREE_ROOT', 'first-id'),),
 
605
            self.get_parents('TREE_ROOT', 'second-id'))