/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: Canonical.com Patch Queue Manager
  • Date: 2009-04-01 15:14:38 UTC
  • mfrom: (4202.3.2 bzr-send-tweak)
  • Revision ID: pqm@pqm.ubuntu.com-20090401151438-hqulqoazddtacbls
(andrew) Don't use get_revision_xml when writing a bundle,
        instead get all the revisions together.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2007 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
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.
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
 
18
import re
18
19
import sys
19
20
 
 
21
import bzrlib
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    errors,
 
25
    osutils,
 
26
    merge,
 
27
    repository,
 
28
    versionedfile,
 
29
    )
20
30
from bzrlib.branch import Branch
21
31
from bzrlib.bzrdir import BzrDir
22
 
from bzrlib.builtins import merge
23
 
import bzrlib.errors
 
32
from bzrlib.repofmt import knitrepo
24
33
from bzrlib.tests import TestCaseWithTransport
25
 
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
 
34
from bzrlib.tests.http_utils import TestCaseWithWebserver
26
35
from bzrlib.tests.test_revision import make_branches
27
36
from bzrlib.trace import mutter
 
37
from bzrlib.upgrade import Convert
28
38
from bzrlib.workingtree import WorkingTree
29
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
 
30
44
 
31
45
def has_revision(branch, revision_id):
32
46
    return branch.repository.has_revision(revision_id)
33
47
 
34
48
def fetch_steps(self, br_a, br_b, writable_a):
35
49
    """A foreign test method for testing fetch locally and remotely."""
36
 
     
 
50
 
37
51
    # TODO RBC 20060201 make this a repository test.
38
52
    repo_b = br_b.repository
39
53
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
40
54
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
41
55
    self.assertEquals(len(br_b.revision_history()), 7)
42
 
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
 
56
    br_b.fetch(br_a, br_a.revision_history()[2])
43
57
    # branch.fetch is not supposed to alter the revision history
44
58
    self.assertEquals(len(br_b.revision_history()), 7)
45
59
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
46
60
 
47
61
    # fetching the next revision up in sample data copies one revision
48
 
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
 
62
    br_b.fetch(br_a, br_a.revision_history()[3])
49
63
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
50
64
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
51
65
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
53
67
    # When a non-branch ancestor is missing, it should be unlisted...
54
68
    # as its not reference from the inventory weave.
55
69
    br_b4 = self.make_branch('br_4')
56
 
    count, failures = br_b4.fetch(br_b)
57
 
    self.assertEqual(count, 7)
58
 
    self.assertEqual(failures, [])
 
70
    br_b4.fetch(br_b)
59
71
 
60
 
    self.assertEqual(writable_a.fetch(br_b)[0], 1)
 
72
    writable_a.fetch(br_b)
61
73
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
62
74
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
63
 
        
 
75
 
64
76
    br_b2 = self.make_branch('br_b2')
65
 
    self.assertEquals(br_b2.fetch(br_b)[0], 7)
 
77
    br_b2.fetch(br_b)
66
78
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
67
79
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
68
80
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
69
81
 
70
82
    br_a2 = self.make_branch('br_a2')
71
 
    self.assertEquals(br_a2.fetch(br_a)[0], 9)
 
83
    br_a2.fetch(br_a)
72
84
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
73
85
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
74
86
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
75
87
 
76
88
    br_a3 = self.make_branch('br_a3')
77
 
    # pulling a branch with no revisions grabs nothing, regardless of 
 
89
    # pulling a branch with no revisions grabs nothing, regardless of
78
90
    # whats in the inventory.
79
 
    self.assertEquals(br_a3.fetch(br_a2)[0], 0)
 
91
    br_a3.fetch(br_a2)
80
92
    for revno in range(4):
81
93
        self.assertFalse(
82
94
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
83
 
    self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
 
95
    br_a3.fetch(br_a2, br_a.revision_history()[2])
84
96
    # pull the 3 revisions introduced by a@u-0-3
85
 
    fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
86
 
    self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
 
97
    br_a3.fetch(br_a2, br_a.revision_history()[3])
87
98
    # InstallFailed should be raised if the branch is missing the revision
88
99
    # that was requested.
89
 
    self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
90
 
    # InstallFailed should be raised if the branch is missing a revision
91
 
    # from its own revision history
92
 
    br_a2.append_revision('a-b-c')
93
 
    self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
 
100
    self.assertRaises(errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
94
101
 
95
 
    # TODO: jam 20051218 Branch should no longer allow append_revision for revisions
96
 
    #       which don't exist. So this test needs to be rewritten
97
 
    #       RBC 20060403 the way to do this is to uncommit the revision from the
98
 
    #           repository after the commit
 
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
107
 
100
108
    #TODO: test that fetch correctly does reweaving when needed. RBC 20051008
101
 
    # Note that this means - updating the weave when ghosts are filled in to 
 
109
    # Note that this means - updating the weave when ghosts are filled in to
102
110
    # add the right parents.
103
111
 
104
112
 
106
114
 
107
115
    def test_fetch(self):
108
116
        #highest indices a: 5, b: 7
109
 
        br_a, br_b = make_branches(self)
 
117
        br_a, br_b = make_branches(self, format='dirstate-tags')
110
118
        fetch_steps(self, br_a, br_b, br_a)
111
119
 
112
120
    def test_fetch_self(self):
113
121
        wt = self.make_branch_and_tree('br')
114
 
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
 
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")
115
177
 
116
178
 
117
179
class TestMergeFetch(TestCaseWithTransport):
125
187
        wt2 = self.make_branch_and_tree('br2')
126
188
        br2 = wt2.branch
127
189
        wt2.commit(message='rev 2-1', rev_id='2-1')
128
 
        merge(other_revision=['br1', -1], base_revision=['br1', 0],
129
 
              this_dir='br2')
 
190
        wt2.merge_from_branch(br1, from_revision='null:')
130
191
        self._check_revs_present(br2)
131
192
 
132
193
    def test_merge_fetches(self):
137
198
        dir_2 = br1.bzrdir.sprout('br2')
138
199
        br2 = dir_2.open_branch()
139
200
        wt1.commit(message='rev 1-2', rev_id='1-2')
140
 
        dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
141
 
        merge(other_revision=['br1', -1], base_revision=[None, None], 
142
 
              this_dir='br2')
 
201
        wt2 = dir_2.open_workingtree()
 
202
        wt2.commit(message='rev 2-1', rev_id='2-1')
 
203
        wt2.merge_from_branch(br1)
143
204
        self._check_revs_present(br2)
144
205
 
145
206
    def _check_revs_present(self, br2):
174
235
    def test_merge_fetches_file_history(self):
175
236
        """Merge brings across file histories"""
176
237
        br2 = Branch.open('br2')
177
 
        merge(other_revision=['br1', -1], base_revision=[None, None], 
178
 
              this_dir='br2')
 
238
        br1 = Branch.open('br1')
 
239
        wt2 = WorkingTree.open('br2').merge_from_branch(br1)
 
240
        br2.lock_read()
 
241
        self.addCleanup(br2.unlock)
179
242
        for rev_id, text in [('1-2', 'original from 1\n'),
180
243
                             ('1-3', 'agreement\n'),
181
244
                             ('2-1', 'contents in 2\n'),
198
261
 
199
262
    def _count_log_matches(self, target, logs):
200
263
        """Count the number of times the target file pattern was fetched in an http log"""
201
 
        log_pattern = '%s HTTP/1.1" 200 - "-" "bzr/%s' % \
202
 
            (target, bzrlib.__version__)
 
264
        get_succeeds_re = re.compile(
 
265
            '.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
 
266
            (     target,                    bzrlib.__version__))
203
267
        c = 0
204
268
        for line in logs:
205
 
            # TODO: perhaps use a regexp instead so we can match more
206
 
            # precisely?
207
 
            if line.find(log_pattern) > -1:
 
269
            if get_succeeds_re.match(line):
208
270
                c += 1
209
271
        return c
210
272
 
211
273
    def test_weaves_are_retrieved_once(self):
212
274
        self.build_tree(("source/", "source/file", "target/"))
213
 
        wt = self.make_branch_and_tree('source')
 
275
        # This test depends on knit dasta storage.
 
276
        wt = self.make_branch_and_tree('source', format='dirstate-tags')
214
277
        branch = wt.branch
215
278
        wt.add(["file"], ["id"])
216
279
        wt.commit("added file")
217
 
        print >>open("source/file", 'w'), "blah"
 
280
        open("source/file", 'w').write("blah\n")
218
281
        wt.commit("changed file")
219
282
        target = BzrDir.create_branch_and_repo("target/")
220
283
        source = Branch.open(self.get_readonly_url("source/"))
221
 
        self.assertEqual(target.fetch(source), (2, []))
222
 
        log_pattern = '%%s HTTP/1.1" 200 - "-" "bzr/%s' % bzrlib.__version__
223
 
        # this is the path to the literal file. As format changes 
 
284
        target.fetch(source)
 
285
        # this is the path to the literal file. As format changes
224
286
        # occur it needs to be updated. FIXME: ask the store for the
225
287
        # path.
226
288
        self.log("web server logs are:")
227
289
        http_logs = self.get_readonly_server().logs
228
290
        self.log('\n'.join(http_logs))
229
 
        self.assertEqual(1, self._count_log_matches('weaves/ce/id.weave', http_logs))
230
 
        self.assertEqual(1, self._count_log_matches('inventory.weave', http_logs))
231
 
        # this r-h check test will prevent regressions, but it currently already 
 
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
232
300
        # passes, before the patch to cache-rh is applied :[
233
 
        self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
 
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))
234
305
        # FIXME naughty poking in there.
235
306
        self.get_readonly_server().logs = []
236
 
        # check there is nothing more to fetch
237
 
        source = Branch.open(self.get_readonly_url("source/"))
238
 
        self.assertEqual(target.fetch(source), (0, []))
 
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)
239
316
        # should make just two requests
240
317
        http_logs = self.get_readonly_server().logs
241
318
        self.log("web server logs are:")
242
319
        self.log('\n'.join(http_logs))
243
 
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs[0:1]))
244
 
        self.assertEqual(1, self._count_log_matches('revision-history', http_logs[1:2]))
245
 
        self.assertEqual(2, len(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.assertTrue(1 >= self._count_log_matches('revision-history',
 
325
                                                     http_logs))
 
326
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
327
                                                     http_logs))
 
328
        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'))