~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testfetch.py

merge merge tweaks from aaron, which includes latest .dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
 
16
import bzrlib.errors
 
17
from bzrlib.selftest.testrevision import make_branches
 
18
from bzrlib.trace import mutter
 
19
from bzrlib.branch import Branch
 
20
import sys
17
21
import os
18
 
import re
19
 
import sys
20
 
 
21
 
import bzrlib
22
 
from bzrlib import (
23
 
    bzrdir,
24
 
    errors,
25
 
    osutils,
26
 
    merge,
27
 
    repository,
28
 
    versionedfile,
29
 
    )
30
 
from bzrlib.branch import Branch
31
 
from bzrlib.bzrdir import BzrDir
32
 
from bzrlib.repofmt import knitrepo
33
 
from bzrlib.tests import TestCaseWithTransport
34
 
from bzrlib.tests.http_utils import TestCaseWithWebserver
35
 
from bzrlib.tests.test_revision import make_branches
36
 
from bzrlib.trace import mutter
37
 
from bzrlib.upgrade import Convert
38
 
from bzrlib.workingtree import WorkingTree
39
 
 
40
 
# These tests are a bit old; please instead add new tests into
41
 
# interrepository_implementations/ so they'll run on all relevant
42
 
# combinations.
43
 
 
44
 
 
45
 
def has_revision(branch, revision_id):
46
 
    return branch.repository.has_revision(revision_id)
47
 
 
48
 
def fetch_steps(self, br_a, br_b, writable_a):
49
 
    """A foreign test method for testing fetch locally and remotely."""
50
 
     
51
 
    # TODO RBC 20060201 make this a repository test.
52
 
    repo_b = br_b.repository
53
 
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
54
 
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
55
 
    self.assertEquals(len(br_b.revision_history()), 7)
56
 
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
57
 
    # branch.fetch is not supposed to alter the revision history
58
 
    self.assertEquals(len(br_b.revision_history()), 7)
59
 
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
60
 
 
61
 
    # fetching the next revision up in sample data copies one revision
62
 
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
63
 
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
64
 
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
65
 
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
66
 
 
67
 
    # When a non-branch ancestor is missing, it should be unlisted...
68
 
    # as its not reference from the inventory weave.
69
 
    br_b4 = self.make_branch('br_4')
70
 
    count, failures = br_b4.fetch(br_b)
71
 
    self.assertEqual(count, 7)
72
 
    self.assertEqual(failures, [])
73
 
 
74
 
    self.assertEqual(writable_a.fetch(br_b)[0], 1)
75
 
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
76
 
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
77
 
        
78
 
    br_b2 = self.make_branch('br_b2')
79
 
    self.assertEquals(br_b2.fetch(br_b)[0], 7)
80
 
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
81
 
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
82
 
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
83
 
 
84
 
    br_a2 = self.make_branch('br_a2')
85
 
    self.assertEquals(br_a2.fetch(br_a)[0], 9)
86
 
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
87
 
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
88
 
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
89
 
 
90
 
    br_a3 = self.make_branch('br_a3')
91
 
    # pulling a branch with no revisions grabs nothing, regardless of 
92
 
    # whats in the inventory.
93
 
    self.assertEquals(br_a3.fetch(br_a2)[0], 0)
94
 
    for revno in range(4):
95
 
        self.assertFalse(
96
 
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
97
 
    self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
98
 
    # pull the 3 revisions introduced by a@u-0-3
99
 
    fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
100
 
    self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
101
 
    # InstallFailed should be raised if the branch is missing the revision
102
 
    # that was requested.
103
 
    self.assertRaises(errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
104
 
 
105
 
    # TODO: Test trying to fetch from a branch that points to a revision not
106
 
    # actually present in its repository.  Not every branch format allows you
107
 
    # to directly point to such revisions, so it's a bit complicated to
108
 
    # construct.  One way would be to uncommit and gc the revision, but not
109
 
    # every branch supports that.  -- mbp 20070814
110
 
 
111
 
    #TODO: test that fetch correctly does reweaving when needed. RBC 20051008
112
 
    # Note that this means - updating the weave when ghosts are filled in to 
113
 
    # add the right parents.
114
 
 
115
 
 
116
 
class TestFetch(TestCaseWithTransport):
117
 
 
118
 
    def test_fetch(self):
119
 
        #highest indices a: 5, b: 7
120
 
        br_a, br_b = make_branches(self, format='dirstate-tags')
121
 
        fetch_steps(self, br_a, br_b, br_a)
122
 
 
123
 
    def test_fetch_self(self):
124
 
        wt = self.make_branch_and_tree('br')
125
 
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
126
 
 
127
 
    def test_fetch_root_knit(self):
128
 
        """Ensure that knit2.fetch() updates the root knit
129
 
        
130
 
        This tests the case where the root has a new revision, but there are no
131
 
        corresponding filename, parent, contents or other changes.
132
 
        """
133
 
        knit1_format = bzrdir.BzrDirMetaFormat1()
134
 
        knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
135
 
        knit2_format = bzrdir.BzrDirMetaFormat1()
136
 
        knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
137
 
        # we start with a knit1 repository because that causes the
138
 
        # root revision to change for each commit, even though the content,
139
 
        # parent, name, and other attributes are unchanged.
140
 
        tree = self.make_branch_and_tree('tree', knit1_format)
141
 
        tree.set_root_id('tree-root')
142
 
        tree.commit('rev1', rev_id='rev1')
143
 
        tree.commit('rev2', rev_id='rev2')
144
 
 
145
 
        # Now we convert it to a knit2 repository so that it has a root knit
146
 
        Convert(tree.basedir, knit2_format)
147
 
        tree = WorkingTree.open(tree.basedir)
148
 
        branch = self.make_branch('branch', format=knit2_format)
149
 
        branch.pull(tree.branch, stop_revision='rev1')
150
 
        repo = branch.repository
151
 
        repo.lock_read()
152
 
        try:
153
 
            # Make sure fetch retrieved only what we requested
154
 
            self.assertEqual({('tree-root', 'rev1'):()},
155
 
                repo.texts.get_parent_map(
156
 
                    [('tree-root', 'rev1'), ('tree-root', 'rev2')]))
157
 
        finally:
158
 
            repo.unlock()
159
 
        branch.pull(tree.branch)
160
 
        # Make sure that the next revision in the root knit was retrieved,
161
 
        # even though the text, name, parent_id, etc., were unchanged.
162
 
        repo.lock_read()
163
 
        try:
164
 
            # Make sure fetch retrieved only what we requested
165
 
            self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
166
 
                repo.texts.get_parent_map([('tree-root', 'rev2')]))
167
 
        finally:
168
 
            repo.unlock()
169
 
 
170
 
    def test_fetch_incompatible(self):
171
 
        knit_tree = self.make_branch_and_tree('knit', format='knit')
172
 
        knit3_tree = self.make_branch_and_tree('knit3',
173
 
            format='dirstate-with-subtree')
174
 
        knit3_tree.commit('blah')
175
 
        e = self.assertRaises(errors.IncompatibleRepositories,
176
 
                              knit_tree.branch.fetch, knit3_tree.branch)
177
 
        self.assertContainsRe(str(e),
178
 
            r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
179
 
            r"different rich-root support")
180
 
 
181
 
 
182
 
class TestMergeFetch(TestCaseWithTransport):
183
 
 
184
 
    def test_merge_fetches_unrelated(self):
185
 
        """Merge brings across history from unrelated source"""
186
 
        wt1 = self.make_branch_and_tree('br1')
187
 
        br1 = wt1.branch
188
 
        wt1.commit(message='rev 1-1', rev_id='1-1')
189
 
        wt1.commit(message='rev 1-2', rev_id='1-2')
190
 
        wt2 = self.make_branch_and_tree('br2')
191
 
        br2 = wt2.branch
192
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
193
 
        wt2.merge_from_branch(br1, from_revision='null:')
194
 
        self._check_revs_present(br2)
195
 
 
196
 
    def test_merge_fetches(self):
197
 
        """Merge brings across history from source"""
198
 
        wt1 = self.make_branch_and_tree('br1')
199
 
        br1 = wt1.branch
200
 
        wt1.commit(message='rev 1-1', rev_id='1-1')
201
 
        dir_2 = br1.bzrdir.sprout('br2')
202
 
        br2 = dir_2.open_branch()
203
 
        wt1.commit(message='rev 1-2', rev_id='1-2')
204
 
        wt2 = dir_2.open_workingtree()
205
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
206
 
        wt2.merge_from_branch(br1)
207
 
        self._check_revs_present(br2)
208
 
 
209
 
    def _check_revs_present(self, br2):
210
 
        for rev_id in '1-1', '1-2', '2-1':
211
 
            self.assertTrue(br2.repository.has_revision(rev_id))
212
 
            rev = br2.repository.get_revision(rev_id)
213
 
            self.assertEqual(rev.revision_id, rev_id)
214
 
            self.assertTrue(br2.repository.get_inventory(rev_id))
215
 
 
216
 
 
217
 
class TestMergeFileHistory(TestCaseWithTransport):
218
 
 
219
 
    def setUp(self):
220
 
        super(TestMergeFileHistory, self).setUp()
221
 
        wt1 = self.make_branch_and_tree('br1')
222
 
        br1 = wt1.branch
223
 
        self.build_tree_contents([('br1/file', 'original contents\n')])
224
 
        wt1.add('file', 'this-file-id')
225
 
        wt1.commit(message='rev 1-1', rev_id='1-1')
226
 
        dir_2 = br1.bzrdir.sprout('br2')
227
 
        br2 = dir_2.open_branch()
228
 
        wt2 = dir_2.open_workingtree()
229
 
        self.build_tree_contents([('br1/file', 'original from 1\n')])
230
 
        wt1.commit(message='rev 1-2', rev_id='1-2')
231
 
        self.build_tree_contents([('br1/file', 'agreement\n')])
232
 
        wt1.commit(message='rev 1-3', rev_id='1-3')
233
 
        self.build_tree_contents([('br2/file', 'contents in 2\n')])
234
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
235
 
        self.build_tree_contents([('br2/file', 'agreement\n')])
236
 
        wt2.commit(message='rev 2-2', rev_id='2-2')
237
 
 
238
 
    def test_merge_fetches_file_history(self):
239
 
        """Merge brings across file histories"""
240
 
        br2 = Branch.open('br2')
241
 
        br1 = Branch.open('br1')
242
 
        wt2 = WorkingTree.open('br2').merge_from_branch(br1)
243
 
        br2.lock_read()
244
 
        self.addCleanup(br2.unlock)
245
 
        for rev_id, text in [('1-2', 'original from 1\n'),
246
 
                             ('1-3', 'agreement\n'),
247
 
                             ('2-1', 'contents in 2\n'),
248
 
                             ('2-2', 'agreement\n')]:
249
 
            self.assertEqualDiff(
250
 
                br2.repository.revision_tree(
251
 
                    rev_id).get_file_text('this-file-id'), text)
252
 
 
253
 
 
254
 
class TestHttpFetch(TestCaseWithWebserver):
255
 
    # FIXME RBC 20060124 this really isn't web specific, perhaps an
256
 
    # instrumented readonly transport? Can we do an instrumented
257
 
    # adapter and use self.get_readonly_url ?
258
 
 
259
 
    def test_fetch(self):
260
 
        #highest indices a: 5, b: 7
261
 
        br_a, br_b = make_branches(self)
262
 
        br_rem_a = Branch.open(self.get_readonly_url('branch1'))
263
 
        fetch_steps(self, br_rem_a, br_b, br_a)
264
 
 
265
 
    def _count_log_matches(self, target, logs):
266
 
        """Count the number of times the target file pattern was fetched in an http log"""
267
 
        get_succeeds_re = re.compile(
268
 
            '.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
269
 
            (     target,                    bzrlib.__version__))
270
 
        c = 0
271
 
        for line in logs:
272
 
            if get_succeeds_re.match(line):
273
 
                c += 1
274
 
        return c
275
 
 
276
 
    def test_weaves_are_retrieved_once(self):
277
 
        self.build_tree(("source/", "source/file", "target/"))
278
 
        # This test depends on knit dasta storage.
279
 
        wt = self.make_branch_and_tree('source', format='dirstate-tags')
280
 
        branch = wt.branch
281
 
        wt.add(["file"], ["id"])
282
 
        wt.commit("added file")
283
 
        open("source/file", 'w').write("blah\n")
284
 
        wt.commit("changed file")
285
 
        target = BzrDir.create_branch_and_repo("target/")
286
 
        source = Branch.open(self.get_readonly_url("source/"))
287
 
        self.assertEqual(target.fetch(source), (2, []))
288
 
        # this is the path to the literal file. As format changes 
289
 
        # occur it needs to be updated. FIXME: ask the store for the
290
 
        # path.
291
 
        self.log("web server logs are:")
292
 
        http_logs = self.get_readonly_server().logs
293
 
        self.log('\n'.join(http_logs))
294
 
        # unfortunately this log entry is branch format specific. We could 
295
 
        # factor out the 'what files does this format use' to a method on the 
296
 
        # repository, which would let us to this generically. RBC 20060419
297
 
        # RBC 20080408: Or perhaps we can assert that no files are fully read
298
 
        # twice?
299
 
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
300
 
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
301
 
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
302
 
        # this r-h check test will prevent regressions, but it currently already 
303
 
        # passes, before the patch to cache-rh is applied :[
304
 
        self.assertTrue(1 >= self._count_log_matches('revision-history',
305
 
                                                     http_logs))
306
 
        self.assertTrue(1 >= self._count_log_matches('last-revision',
307
 
                                                     http_logs))
308
 
        # FIXME naughty poking in there.
309
 
        self.get_readonly_server().logs = []
310
 
        # check there is nothing more to fetch.  We take care to re-use the
311
 
        # existing transport so that the request logs we're about to examine
312
 
        # aren't cluttered with redundant probes for a smart server.
313
 
        # XXX: Perhaps this further parameterisation: test http with smart
314
 
        # server, and test http without smart server?
315
 
        source = Branch.open(
316
 
            self.get_readonly_url("source/"),
317
 
            possible_transports=[source.bzrdir.root_transport])
318
 
        self.assertEqual(target.fetch(source), (0, []))
319
 
        # should make just two requests
320
 
        http_logs = self.get_readonly_server().logs
321
 
        self.log("web server logs are:")
322
 
        self.log('\n'.join(http_logs))
323
 
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
324
 
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
325
 
        self.assertEqual(1, self._count_log_matches('repository/format',
326
 
            http_logs))
327
 
        self.assertTrue(1 >= self._count_log_matches('revision-history',
328
 
                                                     http_logs))
329
 
        self.assertTrue(1 >= self._count_log_matches('last-revision',
330
 
                                                     http_logs))
331
 
        self.assertEqual(4, len(http_logs))
332
 
 
333
 
 
334
 
class TestKnitToPackFetch(TestCaseWithTransport):
335
 
 
336
 
    def find_get_record_stream(self, calls):
337
 
        """In a list of calls, find 'get_record_stream' calls.
338
 
 
339
 
        This also ensures that there is only one get_record_stream call.
340
 
        """
341
 
        get_record_call = None
342
 
        for call in calls:
343
 
            if call[0] == 'get_record_stream':
344
 
                self.assertIs(None, get_record_call,
345
 
                              "there should only be one call to"
346
 
                              " get_record_stream")
347
 
                get_record_call = call
348
 
        self.assertIsNot(None, get_record_call,
349
 
                         "there should be exactly one call to "
350
 
                         " get_record_stream")
351
 
        return get_record_call
352
 
 
353
 
    def test_fetch_with_deltas_no_delta_closure(self):
354
 
        tree = self.make_branch_and_tree('source', format='dirstate')
355
 
        target = self.make_repository('target', format='pack-0.92')
356
 
        self.build_tree(['source/file'])
357
 
        tree.set_root_id('root-id')
358
 
        tree.add('file', 'file-id')
359
 
        tree.commit('one', rev_id='rev-one')
360
 
        source = tree.branch.repository
361
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
362
 
                        source.texts)
363
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
364
 
                        source.signatures)
365
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
366
 
                        source.revisions)
367
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
368
 
                        source.inventories)
369
 
        # precondition
370
 
        self.assertTrue(target._fetch_uses_deltas)
371
 
        target.fetch(source, revision_id='rev-one')
372
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
373
 
                          target._fetch_order, False),
374
 
                         self.find_get_record_stream(source.texts.calls))
375
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
376
 
                          target._fetch_order, False),
377
 
                         self.find_get_record_stream(source.inventories.calls))
378
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
379
 
                          target._fetch_order, False),
380
 
                         self.find_get_record_stream(source.revisions.calls))
381
 
        # XXX: Signatures is special, and slightly broken. The
382
 
        # standard item_keys_introduced_by actually does a lookup for every
383
 
        # signature to see if it exists, rather than waiting to do them all at
384
 
        # once at the end. The fetch code then does an all-at-once and just
385
 
        # allows for some of them to be missing.
386
 
        # So we know there will be extra calls, but the *last* one is the one
387
 
        # we care about.
388
 
        signature_calls = source.signatures.calls[-1:]
389
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
390
 
                          target._fetch_order, False),
391
 
                         self.find_get_record_stream(signature_calls))
392
 
 
393
 
    def test_fetch_no_deltas_with_delta_closure(self):
394
 
        tree = self.make_branch_and_tree('source', format='dirstate')
395
 
        target = self.make_repository('target', format='pack-0.92')
396
 
        self.build_tree(['source/file'])
397
 
        tree.set_root_id('root-id')
398
 
        tree.add('file', 'file-id')
399
 
        tree.commit('one', rev_id='rev-one')
400
 
        source = tree.branch.repository
401
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
402
 
                        source.texts)
403
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
404
 
                        source.signatures)
405
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
406
 
                        source.revisions)
407
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
408
 
                        source.inventories)
409
 
        target._fetch_uses_deltas = False
410
 
        target.fetch(source, revision_id='rev-one')
411
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
412
 
                          target._fetch_order, True),
413
 
                         self.find_get_record_stream(source.texts.calls))
414
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
415
 
                          target._fetch_order, True),
416
 
                         self.find_get_record_stream(source.inventories.calls))
417
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
418
 
                          target._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._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'))
 
22
 
 
23
from bzrlib.selftest import TestCaseInTempDir
 
24
        
 
25
 
 
26
class TestFetch(TestCaseInTempDir):
 
27
    def runTest(self):
 
28
        from bzrlib.fetch import greedy_fetch, has_revision
 
29
 
 
30
        def new_branch(name):
 
31
            os.mkdir(name)
 
32
            return Branch.initialize(name)
 
33
            
 
34
        #highest indices a: 5, b: 7
 
35
        br_a, br_b = make_branches()
 
36
        assert not has_revision(br_b, br_a.revision_history()[3])
 
37
        assert has_revision(br_b, br_a.revision_history()[2])
 
38
        assert len(br_b.revision_history()) == 7
 
39
        assert greedy_fetch(br_b, br_a, br_a.revision_history()[2])[0] == 0
 
40
 
 
41
        # greedy_fetch is not supposed to alter the revision history
 
42
        assert len(br_b.revision_history()) == 7
 
43
        assert not has_revision(br_b, br_a.revision_history()[3])
 
44
 
 
45
        assert len(br_b.revision_history()) == 7
 
46
        assert greedy_fetch(br_b, br_a, br_a.revision_history()[3])[0] == 1
 
47
        assert has_revision(br_b, br_a.revision_history()[3])
 
48
        assert not has_revision(br_a, br_b.revision_history()[3])
 
49
        assert not has_revision(br_a, br_b.revision_history()[4])
 
50
 
 
51
        # When a non-branch ancestor is missing, it should be a failure, not
 
52
        # exception
 
53
        br_a4 = new_branch('br_a4')
 
54
        count, failures = greedy_fetch(br_a4, br_a)
 
55
        assert count == 6
 
56
        assert failures == set((br_b.revision_history()[4],
 
57
                                br_b.revision_history()[5])) 
 
58
 
 
59
        assert greedy_fetch(br_a, br_b)[0] == 4
 
60
        assert has_revision(br_a, br_b.revision_history()[3])
 
61
        assert has_revision(br_a, br_b.revision_history()[4])
 
62
 
 
63
        br_b2 = new_branch('br_b2')
 
64
        assert greedy_fetch(br_b2, br_b)[0] == 7
 
65
        assert has_revision(br_b2, br_b.revision_history()[4])
 
66
        assert has_revision(br_b2, br_a.revision_history()[2])
 
67
        assert not has_revision(br_b2, br_a.revision_history()[3])
 
68
 
 
69
        br_a2 = new_branch('br_a2')
 
70
        assert greedy_fetch(br_a2, br_a)[0] == 9
 
71
        assert has_revision(br_a2, br_b.revision_history()[4])
 
72
        assert has_revision(br_a2, br_a.revision_history()[3])
 
73
 
 
74
        br_a3 = new_branch('br_a3')
 
75
        assert greedy_fetch(br_a3, br_a2)[0] == 0
 
76
        for revno in range(4):
 
77
            assert not has_revision(br_a3, br_a.revision_history()[revno])
 
78
        assert greedy_fetch(br_a3, br_a2, br_a.revision_history()[2])[0] == 3
 
79
        fetched = greedy_fetch(br_a3, br_a2, br_a.revision_history()[3])[0]
 
80
        assert fetched == 3, "fetched %d instead of 3" % fetched
 
81
        # InstallFailed should be raised if the branch is missing the revision
 
82
        # that was requested.
 
83
        self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
 
84
                          br_a2, 'pizza')
 
85
        # InstallFailed should be raised if the branch is missing a revision
 
86
        # from its own revision history
 
87
        br_a2.append_revision('a-b-c')
 
88
        self.assertRaises(bzrlib.errors.InstallFailed, greedy_fetch, br_a3,
 
89
                          br_a2)
 
90
 
 
91
 
 
92
 
 
93
if __name__ == '__main__':
 
94
    import unittest
 
95
    sys.exit(unittest.run_suite(unittest.makeSuite()))