/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 breezy/tests/test_fetch.py

  • Committer: Jelmer Vernooij
  • Date: 2017-12-03 15:14:22 UTC
  • mfrom: (6829.1.1 no-branch-nick)
  • Revision ID: jelmer@jelmer.uk-20171203151422-54pwtld2ae5cx11l
Merge lp:~jelmer/brz/no-branch-nick.

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