~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: Patch Queue Manager
  • Date: 2011-09-22 14:12:18 UTC
  • mfrom: (6155.3.1 jam)
  • Revision ID: pqm@pqm.ubuntu.com-20110922141218-86s4uu6nqvourw4f
(jameinel) Cleanup comments bzrlib/smart/__init__.py (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2011 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
16
 
 
17
 
import os
18
 
import sys
19
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from bzrlib import (
 
18
    bzrdir,
 
19
    errors,
 
20
    osutils,
 
21
    versionedfile,
 
22
    )
20
23
from bzrlib.branch import Branch
21
 
from bzrlib.bzrdir import BzrDir
22
 
from bzrlib.builtins import merge
23
 
import bzrlib.errors
 
24
from bzrlib.repofmt import knitrepo
24
25
from bzrlib.tests import TestCaseWithTransport
25
 
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
26
26
from bzrlib.tests.test_revision import make_branches
27
 
from bzrlib.trace import mutter
 
27
from bzrlib.upgrade import Convert
28
28
from bzrlib.workingtree import WorkingTree
29
29
 
 
30
# These tests are a bit old; please instead add new tests into
 
31
# per_interrepository/ so they'll run on all relevant
 
32
# combinations.
 
33
 
30
34
 
31
35
def has_revision(branch, revision_id):
32
36
    return branch.repository.has_revision(revision_id)
33
37
 
34
38
def fetch_steps(self, br_a, br_b, writable_a):
35
39
    """A foreign test method for testing fetch locally and remotely."""
36
 
     
 
40
 
37
41
    # TODO RBC 20060201 make this a repository test.
38
42
    repo_b = br_b.repository
39
43
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
40
44
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
41
45
    self.assertEquals(len(br_b.revision_history()), 7)
42
 
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
 
46
    br_b.fetch(br_a, br_a.revision_history()[2])
43
47
    # branch.fetch is not supposed to alter the revision history
44
48
    self.assertEquals(len(br_b.revision_history()), 7)
45
49
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
46
50
 
47
51
    # 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)
 
52
    br_b.fetch(br_a, br_a.revision_history()[3])
49
53
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
50
54
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
51
55
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
53
57
    # When a non-branch ancestor is missing, it should be unlisted...
54
58
    # as its not reference from the inventory weave.
55
59
    br_b4 = self.make_branch('br_4')
56
 
    count, failures = br_b4.fetch(br_b)
57
 
    self.assertEqual(count, 7)
58
 
    self.assertEqual(failures, [])
 
60
    br_b4.fetch(br_b)
59
61
 
60
 
    self.assertEqual(writable_a.fetch(br_b)[0], 1)
 
62
    writable_a.fetch(br_b)
61
63
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
62
64
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
63
 
        
 
65
 
64
66
    br_b2 = self.make_branch('br_b2')
65
 
    self.assertEquals(br_b2.fetch(br_b)[0], 7)
 
67
    br_b2.fetch(br_b)
66
68
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
67
69
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
68
70
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
69
71
 
70
72
    br_a2 = self.make_branch('br_a2')
71
 
    self.assertEquals(br_a2.fetch(br_a)[0], 9)
 
73
    br_a2.fetch(br_a)
72
74
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
73
75
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
74
76
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
75
77
 
76
78
    br_a3 = self.make_branch('br_a3')
77
 
    # pulling a branch with no revisions grabs nothing, regardless of 
 
79
    # pulling a branch with no revisions grabs nothing, regardless of
78
80
    # whats in the inventory.
79
 
    self.assertEquals(br_a3.fetch(br_a2)[0], 0)
 
81
    br_a3.fetch(br_a2)
80
82
    for revno in range(4):
81
83
        self.assertFalse(
82
84
            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)
 
85
    br_a3.fetch(br_a2, br_a.revision_history()[2])
84
86
    # 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)
87
 
    # InstallFailed should be raised if the branch is missing the revision
 
87
    br_a3.fetch(br_a2, br_a.revision_history()[3])
 
88
    # NoSuchRevision should be raised if the branch is missing the revision
88
89
    # 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)
 
90
    self.assertRaises(errors.NoSuchRevision, br_a3.fetch, br_a2, 'pizza')
94
91
 
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
 
92
    # TODO: Test trying to fetch from a branch that points to a revision not
 
93
    # actually present in its repository.  Not every branch format allows you
 
94
    # to directly point to such revisions, so it's a bit complicated to
 
95
    # construct.  One way would be to uncommit and gc the revision, but not
 
96
    # every branch supports that.  -- mbp 20070814
99
97
 
100
98
    #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 
 
99
    # Note that this means - updating the weave when ghosts are filled in to
102
100
    # add the right parents.
103
101
 
104
102
 
106
104
 
107
105
    def test_fetch(self):
108
106
        #highest indices a: 5, b: 7
109
 
        br_a, br_b = make_branches(self)
 
107
        br_a, br_b = make_branches(self, format='dirstate-tags')
110
108
        fetch_steps(self, br_a, br_b, br_a)
111
109
 
112
110
    def test_fetch_self(self):
113
111
        wt = self.make_branch_and_tree('br')
114
 
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
 
112
        wt.branch.fetch(wt.branch)
 
113
 
 
114
    def test_fetch_root_knit(self):
 
115
        """Ensure that knit2.fetch() updates the root knit
 
116
 
 
117
        This tests the case where the root has a new revision, but there are no
 
118
        corresponding filename, parent, contents or other changes.
 
119
        """
 
120
        knit1_format = bzrdir.BzrDirMetaFormat1()
 
121
        knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
 
122
        knit2_format = bzrdir.BzrDirMetaFormat1()
 
123
        knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
 
124
        # we start with a knit1 repository because that causes the
 
125
        # root revision to change for each commit, even though the content,
 
126
        # parent, name, and other attributes are unchanged.
 
127
        tree = self.make_branch_and_tree('tree', knit1_format)
 
128
        tree.set_root_id('tree-root')
 
129
        tree.commit('rev1', rev_id='rev1')
 
130
        tree.commit('rev2', rev_id='rev2')
 
131
 
 
132
        # Now we convert it to a knit2 repository so that it has a root knit
 
133
        Convert(tree.basedir, knit2_format)
 
134
        tree = WorkingTree.open(tree.basedir)
 
135
        branch = self.make_branch('branch', format=knit2_format)
 
136
        branch.pull(tree.branch, stop_revision='rev1')
 
137
        repo = branch.repository
 
138
        repo.lock_read()
 
139
        try:
 
140
            # Make sure fetch retrieved only what we requested
 
141
            self.assertEqual({('tree-root', 'rev1'):()},
 
142
                repo.texts.get_parent_map(
 
143
                    [('tree-root', 'rev1'), ('tree-root', 'rev2')]))
 
144
        finally:
 
145
            repo.unlock()
 
146
        branch.pull(tree.branch)
 
147
        # Make sure that the next revision in the root knit was retrieved,
 
148
        # even though the text, name, parent_id, etc., were unchanged.
 
149
        repo.lock_read()
 
150
        try:
 
151
            # Make sure fetch retrieved only what we requested
 
152
            self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
 
153
                repo.texts.get_parent_map([('tree-root', 'rev2')]))
 
154
        finally:
 
155
            repo.unlock()
 
156
 
 
157
    def test_fetch_incompatible(self):
 
158
        knit_tree = self.make_branch_and_tree('knit', format='knit')
 
159
        knit3_tree = self.make_branch_and_tree('knit3',
 
160
            format='dirstate-with-subtree')
 
161
        knit3_tree.commit('blah')
 
162
        e = self.assertRaises(errors.IncompatibleRepositories,
 
163
                              knit_tree.branch.fetch, knit3_tree.branch)
 
164
        self.assertContainsRe(str(e),
 
165
            r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
 
166
            r"different rich-root support")
115
167
 
116
168
 
117
169
class TestMergeFetch(TestCaseWithTransport):
125
177
        wt2 = self.make_branch_and_tree('br2')
126
178
        br2 = wt2.branch
127
179
        wt2.commit(message='rev 2-1', rev_id='2-1')
128
 
        merge(other_revision=['br1', -1], base_revision=['br1', 0],
129
 
              this_dir='br2')
 
180
        wt2.merge_from_branch(br1, from_revision='null:')
130
181
        self._check_revs_present(br2)
131
182
 
132
183
    def test_merge_fetches(self):
137
188
        dir_2 = br1.bzrdir.sprout('br2')
138
189
        br2 = dir_2.open_branch()
139
190
        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')
 
191
        wt2 = dir_2.open_workingtree()
 
192
        wt2.commit(message='rev 2-1', rev_id='2-1')
 
193
        wt2.merge_from_branch(br1)
143
194
        self._check_revs_present(br2)
144
195
 
145
196
    def _check_revs_present(self, br2):
174
225
    def test_merge_fetches_file_history(self):
175
226
        """Merge brings across file histories"""
176
227
        br2 = Branch.open('br2')
177
 
        merge(other_revision=['br1', -1], base_revision=[None, None], 
178
 
              this_dir='br2')
 
228
        br1 = Branch.open('br1')
 
229
        wt2 = WorkingTree.open('br2').merge_from_branch(br1)
 
230
        br2.lock_read()
 
231
        self.addCleanup(br2.unlock)
179
232
        for rev_id, text in [('1-2', 'original from 1\n'),
180
233
                             ('1-3', 'agreement\n'),
181
234
                             ('2-1', 'contents in 2\n'),
185
238
                    rev_id).get_file_text('this-file-id'), text)
186
239
 
187
240
 
188
 
class TestHttpFetch(TestCaseWithWebserver):
189
 
    # FIXME RBC 20060124 this really isn't web specific, perhaps an
190
 
    # instrumented readonly transport? Can we do an instrumented
191
 
    # adapter and use self.get_readonly_url ?
192
 
 
193
 
    def test_fetch(self):
194
 
        #highest indices a: 5, b: 7
195
 
        br_a, br_b = make_branches(self)
196
 
        br_rem_a = Branch.open(self.get_readonly_url('branch1'))
197
 
        fetch_steps(self, br_rem_a, br_b, br_a)
198
 
 
199
 
    def _count_log_matches(self, target, logs):
200
 
        """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__)
203
 
        c = 0
204
 
        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:
208
 
                c += 1
209
 
        return c
210
 
 
211
 
    def test_weaves_are_retrieved_once(self):
212
 
        self.build_tree(("source/", "source/file", "target/"))
213
 
        wt = self.make_branch_and_tree('source')
214
 
        branch = wt.branch
215
 
        wt.add(["file"], ["id"])
216
 
        wt.commit("added file")
217
 
        print >>open("source/file", 'w'), "blah"
218
 
        wt.commit("changed file")
219
 
        target = BzrDir.create_branch_and_repo("target/")
220
 
        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 
224
 
        # occur it needs to be updated. FIXME: ask the store for the
225
 
        # path.
226
 
        self.log("web server logs are:")
227
 
        http_logs = self.get_readonly_server().logs
228
 
        self.log('\n'.join(http_logs))
229
 
        # unfortunately this log entry is branch format specific. We could 
230
 
        # factor out the 'what files does this format use' to a method on the 
231
 
        # repository, which would let us to this generically. RBC 20060419
232
 
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
233
 
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
234
 
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
235
 
        self.assertEqual(1, self._count_log_matches('inventory.knit', http_logs))
236
 
        # this r-h check test will prevent regressions, but it currently already 
237
 
        # passes, before the patch to cache-rh is applied :[
238
 
        self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
239
 
        # FIXME naughty poking in there.
240
 
        self.get_readonly_server().logs = []
241
 
        # check there is nothing more to fetch
242
 
        source = Branch.open(self.get_readonly_url("source/"))
243
 
        self.assertEqual(target.fetch(source), (0, []))
244
 
        # should make just two requests
245
 
        http_logs = self.get_readonly_server().logs
246
 
        self.log("web server logs are:")
247
 
        self.log('\n'.join(http_logs))
248
 
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
249
 
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
250
 
        self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
251
 
        self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
252
 
        self.assertEqual(4, len(http_logs))
 
241
class TestKnitToPackFetch(TestCaseWithTransport):
 
242
 
 
243
    def find_get_record_stream(self, calls, expected_count=1):
 
244
        """In a list of calls, find the last 'get_record_stream'.
 
245
 
 
246
        :param expected_count: The number of calls we should exepect to find.
 
247
            If a different number is found, an assertion is raised.
 
248
        """
 
249
        get_record_call = None
 
250
        call_count = 0
 
251
        for call in calls:
 
252
            if call[0] == 'get_record_stream':
 
253
                call_count += 1
 
254
                get_record_call = call
 
255
        self.assertEqual(expected_count, call_count)
 
256
        return get_record_call
 
257
 
 
258
    def test_fetch_with_deltas_no_delta_closure(self):
 
259
        tree = self.make_branch_and_tree('source', format='dirstate')
 
260
        target = self.make_repository('target', format='pack-0.92')
 
261
        self.build_tree(['source/file'])
 
262
        tree.set_root_id('root-id')
 
263
        tree.add('file', 'file-id')
 
264
        tree.commit('one', rev_id='rev-one')
 
265
        source = tree.branch.repository
 
266
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
 
267
                        source.texts)
 
268
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
 
269
                        source.signatures)
 
270
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
 
271
                        source.revisions)
 
272
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
 
273
                        source.inventories)
 
274
        # precondition
 
275
        self.assertTrue(target._format._fetch_uses_deltas)
 
276
        target.fetch(source, revision_id='rev-one')
 
277
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
 
278
                          target._format._fetch_order, False),
 
279
                         self.find_get_record_stream(source.texts.calls))
 
280
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
281
          target._format._fetch_order, False),
 
282
          self.find_get_record_stream(source.inventories.calls, 2))
 
283
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
284
                          target._format._fetch_order, False),
 
285
                         self.find_get_record_stream(source.revisions.calls))
 
286
        # XXX: Signatures is special, and slightly broken. The
 
287
        # standard item_keys_introduced_by actually does a lookup for every
 
288
        # signature to see if it exists, rather than waiting to do them all at
 
289
        # once at the end. The fetch code then does an all-at-once and just
 
290
        # allows for some of them to be missing.
 
291
        # So we know there will be extra calls, but the *last* one is the one
 
292
        # we care about.
 
293
        signature_calls = source.signatures.calls[-1:]
 
294
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
295
                          target._format._fetch_order, False),
 
296
                         self.find_get_record_stream(signature_calls))
 
297
 
 
298
    def test_fetch_no_deltas_with_delta_closure(self):
 
299
        tree = self.make_branch_and_tree('source', format='dirstate')
 
300
        target = self.make_repository('target', format='pack-0.92')
 
301
        self.build_tree(['source/file'])
 
302
        tree.set_root_id('root-id')
 
303
        tree.add('file', 'file-id')
 
304
        tree.commit('one', rev_id='rev-one')
 
305
        source = tree.branch.repository
 
306
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
 
307
                        source.texts)
 
308
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
 
309
                        source.signatures)
 
310
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
 
311
                        source.revisions)
 
312
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
 
313
                        source.inventories)
 
314
        # XXX: This won't work in general, but for the dirstate format it does.
 
315
        self.overrideAttr(target._format, '_fetch_uses_deltas', False)
 
316
        target.fetch(source, revision_id='rev-one')
 
317
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
 
318
                          target._format._fetch_order, True),
 
319
                         self.find_get_record_stream(source.texts.calls))
 
320
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
321
            target._format._fetch_order, True),
 
322
            self.find_get_record_stream(source.inventories.calls, 2))
 
323
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
324
                          target._format._fetch_order, True),
 
325
                         self.find_get_record_stream(source.revisions.calls))
 
326
        # XXX: Signatures is special, and slightly broken. The
 
327
        # standard item_keys_introduced_by actually does a lookup for every
 
328
        # signature to see if it exists, rather than waiting to do them all at
 
329
        # once at the end. The fetch code then does an all-at-once and just
 
330
        # allows for some of them to be missing.
 
331
        # So we know there will be extra calls, but the *last* one is the one
 
332
        # we care about.
 
333
        signature_calls = source.signatures.calls[-1:]
 
334
        self.assertEqual(('get_record_stream', [('rev-one',)],
 
335
                          target._format._fetch_order, True),
 
336
                         self.find_get_record_stream(signature_calls))
 
337
 
 
338
    def test_fetch_revisions_with_deltas_into_pack(self):
 
339
        # See BUG #261339, dev versions of bzr could accidentally create deltas
 
340
        # in revision texts in knit branches (when fetching from packs). So we
 
341
        # ensure that *if* a knit repository has a delta in revisions, that it
 
342
        # gets properly expanded back into a fulltext when stored in the pack
 
343
        # file.
 
344
        tree = self.make_branch_and_tree('source', format='dirstate')
 
345
        target = self.make_repository('target', format='pack-0.92')
 
346
        self.build_tree(['source/file'])
 
347
        tree.set_root_id('root-id')
 
348
        tree.add('file', 'file-id')
 
349
        tree.commit('one', rev_id='rev-one')
 
350
        # Hack the KVF for revisions so that it "accidentally" allows a delta
 
351
        tree.branch.repository.revisions._max_delta_chain = 200
 
352
        tree.commit('two', rev_id='rev-two')
 
353
        source = tree.branch.repository
 
354
        # Ensure that we stored a delta
 
355
        source.lock_read()
 
356
        self.addCleanup(source.unlock)
 
357
        record = source.revisions.get_record_stream([('rev-two',)],
 
358
            'unordered', False).next()
 
359
        self.assertEqual('knit-delta-gz', record.storage_kind)
 
360
        target.fetch(tree.branch.repository, revision_id='rev-two')
 
361
        # The record should get expanded back to a fulltext
 
362
        target.lock_read()
 
363
        self.addCleanup(target.unlock)
 
364
        record = target.revisions.get_record_stream([('rev-two',)],
 
365
            'unordered', False).next()
 
366
        self.assertEqual('knit-ft-gz', record.storage_kind)
 
367
 
 
368
    def test_fetch_with_fallback_and_merge(self):
 
369
        builder = self.make_branch_builder('source', format='pack-0.92')
 
370
        builder.start_series()
 
371
        # graph
 
372
        #   A
 
373
        #   |\
 
374
        #   B C
 
375
        #   | |
 
376
        #   | D
 
377
        #   | |
 
378
        #   | E
 
379
        #    \|
 
380
        #     F
 
381
        # A & B are present in the base (stacked-on) repository, A-E are
 
382
        # present in the source.
 
383
        # This reproduces bug #304841
 
384
        # We need a large enough inventory that total size of compressed deltas
 
385
        # is shorter than the size of a compressed fulltext. We have to use
 
386
        # random ids because otherwise the inventory fulltext compresses too
 
387
        # well and the deltas get bigger.
 
388
        to_add = [
 
389
            ('add', ('', 'TREE_ROOT', 'directory', None))]
 
390
        for i in xrange(10):
 
391
            fname = 'file%03d' % (i,)
 
392
            fileid = '%s-%s' % (fname, osutils.rand_chars(64))
 
393
            to_add.append(('add', (fname, fileid, 'file', 'content\n')))
 
394
        builder.build_snapshot('A', None, to_add)
 
395
        builder.build_snapshot('B', ['A'], [])
 
396
        builder.build_snapshot('C', ['A'], [])
 
397
        builder.build_snapshot('D', ['C'], [])
 
398
        builder.build_snapshot('E', ['D'], [])
 
399
        builder.build_snapshot('F', ['E', 'B'], [])
 
400
        builder.finish_series()
 
401
        source_branch = builder.get_branch()
 
402
        source_branch.bzrdir.sprout('base', revision_id='B')
 
403
        target_branch = self.make_branch('target', format='1.6')
 
404
        target_branch.set_stacked_on_url('../base')
 
405
        source = source_branch.repository
 
406
        source.lock_read()
 
407
        self.addCleanup(source.unlock)
 
408
        source.inventories = versionedfile.OrderingVersionedFilesDecorator(
 
409
                        source.inventories,
 
410
                        key_priority={('E',): 1, ('D',): 2, ('C',): 4,
 
411
                                      ('F',): 3})
 
412
        # Ensure that the content is yielded in the proper order, and given as
 
413
        # the expected kinds
 
414
        records = [(record.key, record.storage_kind)
 
415
                   for record in source.inventories.get_record_stream(
 
416
                        [('D',), ('C',), ('E',), ('F',)], 'unordered', False)]
 
417
        self.assertEqual([(('E',), 'knit-delta-gz'), (('D',), 'knit-delta-gz'),
 
418
                          (('F',), 'knit-delta-gz'), (('C',), 'knit-delta-gz')],
 
419
                          records)
 
420
 
 
421
        target_branch.lock_write()
 
422
        self.addCleanup(target_branch.unlock)
 
423
        target = target_branch.repository
 
424
        target.fetch(source, revision_id='F')
 
425
        # 'C' should be expanded to a fulltext, but D and E should still be
 
426
        # deltas
 
427
        stream = target.inventories.get_record_stream(
 
428
            [('C',), ('D',), ('E',), ('F',)],
 
429
            'unordered', False)
 
430
        kinds = dict((record.key, record.storage_kind) for record in stream)
 
431
        self.assertEqual({('C',): 'knit-ft-gz', ('D',): 'knit-delta-gz',
 
432
                          ('E',): 'knit-delta-gz', ('F',): 'knit-delta-gz'},
 
433
                         kinds)
 
434
 
 
435
 
 
436
class Test1To2Fetch(TestCaseWithTransport):
 
437
    """Tests for Model1To2 failure modes"""
 
438
 
 
439
    def make_tree_and_repo(self):
 
440
        self.tree = self.make_branch_and_tree('tree', format='pack-0.92')
 
441
        self.repo = self.make_repository('rich-repo', format='rich-root-pack')
 
442
        self.repo.lock_write()
 
443
        self.addCleanup(self.repo.unlock)
 
444
 
 
445
    def do_fetch_order_test(self, first, second):
 
446
        """Test that fetch works no matter what the set order of revision is.
 
447
 
 
448
        This test depends on the order of items in a set, which is
 
449
        implementation-dependant, so we test A, B and then B, A.
 
450
        """
 
451
        self.make_tree_and_repo()
 
452
        self.tree.commit('Commit 1', rev_id=first)
 
453
        self.tree.commit('Commit 2', rev_id=second)
 
454
        self.repo.fetch(self.tree.branch.repository, second)
 
455
 
 
456
    def test_fetch_order_AB(self):
 
457
        """See do_fetch_order_test"""
 
458
        self.do_fetch_order_test('A', 'B')
 
459
 
 
460
    def test_fetch_order_BA(self):
 
461
        """See do_fetch_order_test"""
 
462
        self.do_fetch_order_test('B', 'A')
 
463
 
 
464
    def get_parents(self, file_id, revision_id):
 
465
        self.repo.lock_read()
 
466
        try:
 
467
            parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
 
468
            return parent_map[(file_id, revision_id)]
 
469
        finally:
 
470
            self.repo.unlock()
 
471
 
 
472
    def test_fetch_ghosts(self):
 
473
        self.make_tree_and_repo()
 
474
        self.tree.commit('first commit', rev_id='left-parent')
 
475
        self.tree.add_parent_tree_id('ghost-parent')
 
476
        fork = self.tree.bzrdir.sprout('fork', 'null:').open_workingtree()
 
477
        fork.commit('not a ghost', rev_id='not-ghost-parent')
 
478
        self.tree.branch.repository.fetch(fork.branch.repository,
 
479
                                     'not-ghost-parent')
 
480
        self.tree.add_parent_tree_id('not-ghost-parent')
 
481
        self.tree.commit('second commit', rev_id='second-id')
 
482
        self.repo.fetch(self.tree.branch.repository, 'second-id')
 
483
        root_id = self.tree.get_root_id()
 
484
        self.assertEqual(
 
485
            ((root_id, 'left-parent'), (root_id, 'not-ghost-parent')),
 
486
            self.get_parents(root_id, 'second-id'))
 
487
 
 
488
    def make_two_commits(self, change_root, fetch_twice):
 
489
        self.make_tree_and_repo()
 
490
        self.tree.commit('first commit', rev_id='first-id')
 
491
        if change_root:
 
492
            self.tree.set_root_id('unique-id')
 
493
        self.tree.commit('second commit', rev_id='second-id')
 
494
        if fetch_twice:
 
495
            self.repo.fetch(self.tree.branch.repository, 'first-id')
 
496
        self.repo.fetch(self.tree.branch.repository, 'second-id')
 
497
 
 
498
    def test_fetch_changed_root(self):
 
499
        self.make_two_commits(change_root=True, fetch_twice=False)
 
500
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
 
501
 
 
502
    def test_two_fetch_changed_root(self):
 
503
        self.make_two_commits(change_root=True, fetch_twice=True)
 
504
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
 
505
 
 
506
    def test_two_fetches(self):
 
507
        self.make_two_commits(change_root=False, fetch_twice=True)
 
508
        self.assertEqual((('TREE_ROOT', 'first-id'),),
 
509
            self.get_parents('TREE_ROOT', 'second-id'))