/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-09 21:42:24 UTC
  • mto: This revision was merged to the branch mainline in revision 3543.
  • Revision ID: john@arbash-meinel.com-20080709214224-r75k87r6a01pfc3h
Restore a real weave merge to 'bzr merge --weave'.

To do so efficiently, we only add the simple LCAs to the final weave
object, unless we run into complexities with the merge graph.
This gives the same effective result as adding all the texts,
with the advantage of not having to extract all of them.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007, 2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2007 Canonical Ltd
2
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
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
18
import re
22
22
from bzrlib import (
23
23
    bzrdir,
24
24
    errors,
25
 
    osutils,
26
25
    merge,
27
26
    repository,
28
 
    versionedfile,
29
27
    )
30
28
from bzrlib.branch import Branch
31
29
from bzrlib.bzrdir import BzrDir
32
30
from bzrlib.repofmt import knitrepo
33
31
from bzrlib.tests import TestCaseWithTransport
 
32
from bzrlib.tests.http_utils import TestCaseWithWebserver
34
33
from bzrlib.tests.test_revision import make_branches
35
34
from bzrlib.trace import mutter
36
35
from bzrlib.upgrade import Convert
37
36
from bzrlib.workingtree import WorkingTree
38
37
 
39
38
# These tests are a bit old; please instead add new tests into
40
 
# per_interrepository/ so they'll run on all relevant
 
39
# interrepository_implementations/ so they'll run on all relevant
41
40
# combinations.
42
41
 
43
42
 
46
45
 
47
46
def fetch_steps(self, br_a, br_b, writable_a):
48
47
    """A foreign test method for testing fetch locally and remotely."""
49
 
 
 
48
     
50
49
    # TODO RBC 20060201 make this a repository test.
51
50
    repo_b = br_b.repository
52
51
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
53
52
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
54
53
    self.assertEquals(len(br_b.revision_history()), 7)
55
 
    br_b.fetch(br_a, br_a.revision_history()[2])
 
54
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
56
55
    # branch.fetch is not supposed to alter the revision history
57
56
    self.assertEquals(len(br_b.revision_history()), 7)
58
57
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
59
58
 
60
59
    # fetching the next revision up in sample data copies one revision
61
 
    br_b.fetch(br_a, br_a.revision_history()[3])
 
60
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
62
61
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
63
62
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
64
63
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
66
65
    # When a non-branch ancestor is missing, it should be unlisted...
67
66
    # as its not reference from the inventory weave.
68
67
    br_b4 = self.make_branch('br_4')
69
 
    br_b4.fetch(br_b)
 
68
    count, failures = br_b4.fetch(br_b)
 
69
    self.assertEqual(count, 7)
 
70
    self.assertEqual(failures, [])
70
71
 
71
 
    writable_a.fetch(br_b)
 
72
    self.assertEqual(writable_a.fetch(br_b)[0], 1)
72
73
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
73
74
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
74
 
 
 
75
        
75
76
    br_b2 = self.make_branch('br_b2')
76
 
    br_b2.fetch(br_b)
 
77
    self.assertEquals(br_b2.fetch(br_b)[0], 7)
77
78
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
78
79
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
79
80
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
80
81
 
81
82
    br_a2 = self.make_branch('br_a2')
82
 
    br_a2.fetch(br_a)
 
83
    self.assertEquals(br_a2.fetch(br_a)[0], 9)
83
84
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
84
85
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
85
86
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
86
87
 
87
88
    br_a3 = self.make_branch('br_a3')
88
 
    # pulling a branch with no revisions grabs nothing, regardless of
 
89
    # pulling a branch with no revisions grabs nothing, regardless of 
89
90
    # whats in the inventory.
90
 
    br_a3.fetch(br_a2)
 
91
    self.assertEquals(br_a3.fetch(br_a2)[0], 0)
91
92
    for revno in range(4):
92
93
        self.assertFalse(
93
94
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
94
 
    br_a3.fetch(br_a2, br_a.revision_history()[2])
 
95
    self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
95
96
    # pull the 3 revisions introduced by a@u-0-3
96
 
    br_a3.fetch(br_a2, br_a.revision_history()[3])
97
 
    # NoSuchRevision should be raised if the branch is missing the revision
 
97
    fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
 
98
    self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
 
99
    # InstallFailed should be raised if the branch is missing the revision
98
100
    # that was requested.
99
 
    self.assertRaises(errors.NoSuchRevision, br_a3.fetch, br_a2, 'pizza')
 
101
    self.assertRaises(errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
100
102
 
101
103
    # TODO: Test trying to fetch from a branch that points to a revision not
102
104
    # actually present in its repository.  Not every branch format allows you
105
107
    # every branch supports that.  -- mbp 20070814
106
108
 
107
109
    #TODO: test that fetch correctly does reweaving when needed. RBC 20051008
108
 
    # Note that this means - updating the weave when ghosts are filled in to
 
110
    # Note that this means - updating the weave when ghosts are filled in to 
109
111
    # add the right parents.
110
112
 
111
113
 
118
120
 
119
121
    def test_fetch_self(self):
120
122
        wt = self.make_branch_and_tree('br')
121
 
        wt.branch.fetch(wt.branch)
 
123
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
122
124
 
123
125
    def test_fetch_root_knit(self):
124
126
        """Ensure that knit2.fetch() updates the root knit
125
 
 
 
127
        
126
128
        This tests the case where the root has a new revision, but there are no
127
129
        corresponding filename, parent, contents or other changes.
128
130
        """
168
170
        knit3_tree = self.make_branch_and_tree('knit3',
169
171
            format='dirstate-with-subtree')
170
172
        knit3_tree.commit('blah')
171
 
        e = self.assertRaises(errors.IncompatibleRepositories,
172
 
                              knit_tree.branch.fetch, knit3_tree.branch)
173
 
        self.assertContainsRe(str(e),
174
 
            r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
175
 
            r"different rich-root support")
 
173
        self.assertRaises(errors.IncompatibleRepositories,
 
174
                          knit_tree.branch.fetch, knit3_tree.branch)
176
175
 
177
176
 
178
177
class TestMergeFetch(TestCaseWithTransport):
247
246
                    rev_id).get_file_text('this-file-id'), text)
248
247
 
249
248
 
250
 
class TestKnitToPackFetch(TestCaseWithTransport):
251
 
 
252
 
    def find_get_record_stream(self, calls, expected_count=1):
253
 
        """In a list of calls, find the last 'get_record_stream'.
254
 
 
255
 
        :param expected_count: The number of calls we should exepect to find.
256
 
            If a different number is found, an assertion is raised.
257
 
        """
258
 
        get_record_call = None
259
 
        call_count = 0
260
 
        for call in calls:
261
 
            if call[0] == 'get_record_stream':
262
 
                call_count += 1
263
 
                get_record_call = call
264
 
        self.assertEqual(expected_count, call_count)
265
 
        return get_record_call
266
 
 
267
 
    def test_fetch_with_deltas_no_delta_closure(self):
268
 
        tree = self.make_branch_and_tree('source', format='dirstate')
269
 
        target = self.make_repository('target', format='pack-0.92')
270
 
        self.build_tree(['source/file'])
271
 
        tree.set_root_id('root-id')
272
 
        tree.add('file', 'file-id')
273
 
        tree.commit('one', rev_id='rev-one')
274
 
        source = tree.branch.repository
275
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
276
 
                        source.texts)
277
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
278
 
                        source.signatures)
279
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
280
 
                        source.revisions)
281
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
282
 
                        source.inventories)
283
 
        # precondition
284
 
        self.assertTrue(target._format._fetch_uses_deltas)
285
 
        target.fetch(source, revision_id='rev-one')
286
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
287
 
                          target._format._fetch_order, False),
288
 
                         self.find_get_record_stream(source.texts.calls))
289
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
290
 
          target._format._fetch_order, False),
291
 
          self.find_get_record_stream(source.inventories.calls, 2))
292
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
293
 
                          target._format._fetch_order, False),
294
 
                         self.find_get_record_stream(source.revisions.calls))
295
 
        # XXX: Signatures is special, and slightly broken. The
296
 
        # standard item_keys_introduced_by actually does a lookup for every
297
 
        # signature to see if it exists, rather than waiting to do them all at
298
 
        # once at the end. The fetch code then does an all-at-once and just
299
 
        # allows for some of them to be missing.
300
 
        # So we know there will be extra calls, but the *last* one is the one
301
 
        # we care about.
302
 
        signature_calls = source.signatures.calls[-1:]
303
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
304
 
                          target._format._fetch_order, False),
305
 
                         self.find_get_record_stream(signature_calls))
306
 
 
307
 
    def test_fetch_no_deltas_with_delta_closure(self):
308
 
        tree = self.make_branch_and_tree('source', format='dirstate')
309
 
        target = self.make_repository('target', format='pack-0.92')
310
 
        self.build_tree(['source/file'])
311
 
        tree.set_root_id('root-id')
312
 
        tree.add('file', 'file-id')
313
 
        tree.commit('one', rev_id='rev-one')
314
 
        source = tree.branch.repository
315
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
316
 
                        source.texts)
317
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
318
 
                        source.signatures)
319
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
320
 
                        source.revisions)
321
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
322
 
                        source.inventories)
323
 
        # XXX: This won't work in general, but for the dirstate format it does.
324
 
        self.overrideAttr(target._format, '_fetch_uses_deltas', False)
325
 
        target.fetch(source, revision_id='rev-one')
326
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
327
 
                          target._format._fetch_order, True),
328
 
                         self.find_get_record_stream(source.texts.calls))
329
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
330
 
            target._format._fetch_order, True),
331
 
            self.find_get_record_stream(source.inventories.calls, 2))
332
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
333
 
                          target._format._fetch_order, True),
334
 
                         self.find_get_record_stream(source.revisions.calls))
335
 
        # XXX: Signatures is special, and slightly broken. The
336
 
        # standard item_keys_introduced_by actually does a lookup for every
337
 
        # signature to see if it exists, rather than waiting to do them all at
338
 
        # once at the end. The fetch code then does an all-at-once and just
339
 
        # allows for some of them to be missing.
340
 
        # So we know there will be extra calls, but the *last* one is the one
341
 
        # we care about.
342
 
        signature_calls = source.signatures.calls[-1:]
343
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
344
 
                          target._format._fetch_order, True),
345
 
                         self.find_get_record_stream(signature_calls))
346
 
 
347
 
    def test_fetch_revisions_with_deltas_into_pack(self):
348
 
        # See BUG #261339, dev versions of bzr could accidentally create deltas
349
 
        # in revision texts in knit branches (when fetching from packs). So we
350
 
        # ensure that *if* a knit repository has a delta in revisions, that it
351
 
        # gets properly expanded back into a fulltext when stored in the pack
352
 
        # file.
353
 
        tree = self.make_branch_and_tree('source', format='dirstate')
354
 
        target = self.make_repository('target', format='pack-0.92')
355
 
        self.build_tree(['source/file'])
356
 
        tree.set_root_id('root-id')
357
 
        tree.add('file', 'file-id')
358
 
        tree.commit('one', rev_id='rev-one')
359
 
        # Hack the KVF for revisions so that it "accidentally" allows a delta
360
 
        tree.branch.repository.revisions._max_delta_chain = 200
361
 
        tree.commit('two', rev_id='rev-two')
362
 
        source = tree.branch.repository
363
 
        # Ensure that we stored a delta
364
 
        source.lock_read()
365
 
        self.addCleanup(source.unlock)
366
 
        record = source.revisions.get_record_stream([('rev-two',)],
367
 
            'unordered', False).next()
368
 
        self.assertEqual('knit-delta-gz', record.storage_kind)
369
 
        target.fetch(tree.branch.repository, revision_id='rev-two')
370
 
        # The record should get expanded back to a fulltext
371
 
        target.lock_read()
372
 
        self.addCleanup(target.unlock)
373
 
        record = target.revisions.get_record_stream([('rev-two',)],
374
 
            'unordered', False).next()
375
 
        self.assertEqual('knit-ft-gz', record.storage_kind)
376
 
 
377
 
    def test_fetch_with_fallback_and_merge(self):
378
 
        builder = self.make_branch_builder('source', format='pack-0.92')
379
 
        builder.start_series()
380
 
        # graph
381
 
        #   A
382
 
        #   |\
383
 
        #   B C
384
 
        #   | |
385
 
        #   | D
386
 
        #   | |
387
 
        #   | E
388
 
        #    \|
389
 
        #     F
390
 
        # A & B are present in the base (stacked-on) repository, A-E are
391
 
        # present in the source.
392
 
        # This reproduces bug #304841
393
 
        # We need a large enough inventory that total size of compressed deltas
394
 
        # is shorter than the size of a compressed fulltext. We have to use
395
 
        # random ids because otherwise the inventory fulltext compresses too
396
 
        # well and the deltas get bigger.
397
 
        to_add = [
398
 
            ('add', ('', 'TREE_ROOT', 'directory', None))]
399
 
        for i in xrange(10):
400
 
            fname = 'file%03d' % (i,)
401
 
            fileid = '%s-%s' % (fname, osutils.rand_chars(64))
402
 
            to_add.append(('add', (fname, fileid, 'file', 'content\n')))
403
 
        builder.build_snapshot('A', None, to_add)
404
 
        builder.build_snapshot('B', ['A'], [])
405
 
        builder.build_snapshot('C', ['A'], [])
406
 
        builder.build_snapshot('D', ['C'], [])
407
 
        builder.build_snapshot('E', ['D'], [])
408
 
        builder.build_snapshot('F', ['E', 'B'], [])
409
 
        builder.finish_series()
410
 
        source_branch = builder.get_branch()
411
 
        source_branch.bzrdir.sprout('base', revision_id='B')
412
 
        target_branch = self.make_branch('target', format='1.6')
413
 
        target_branch.set_stacked_on_url('../base')
414
 
        source = source_branch.repository
415
 
        source.lock_read()
416
 
        self.addCleanup(source.unlock)
417
 
        source.inventories = versionedfile.OrderingVersionedFilesDecorator(
418
 
                        source.inventories,
419
 
                        key_priority={('E',): 1, ('D',): 2, ('C',): 4,
420
 
                                      ('F',): 3})
421
 
        # Ensure that the content is yielded in the proper order, and given as
422
 
        # the expected kinds
423
 
        records = [(record.key, record.storage_kind)
424
 
                   for record in source.inventories.get_record_stream(
425
 
                        [('D',), ('C',), ('E',), ('F',)], 'unordered', False)]
426
 
        self.assertEqual([(('E',), 'knit-delta-gz'), (('D',), 'knit-delta-gz'),
427
 
                          (('F',), 'knit-delta-gz'), (('C',), 'knit-delta-gz')],
428
 
                          records)
429
 
 
430
 
        target_branch.lock_write()
431
 
        self.addCleanup(target_branch.unlock)
432
 
        target = target_branch.repository
433
 
        target.fetch(source, revision_id='F')
434
 
        # 'C' should be expanded to a fulltext, but D and E should still be
435
 
        # deltas
436
 
        stream = target.inventories.get_record_stream(
437
 
            [('C',), ('D',), ('E',), ('F',)],
438
 
            'unordered', False)
439
 
        kinds = dict((record.key, record.storage_kind) for record in stream)
440
 
        self.assertEqual({('C',): 'knit-ft-gz', ('D',): 'knit-delta-gz',
441
 
                          ('E',): 'knit-delta-gz', ('F',): 'knit-delta-gz'},
442
 
                         kinds)
 
249
class TestHttpFetch(TestCaseWithWebserver):
 
250
    # FIXME RBC 20060124 this really isn't web specific, perhaps an
 
251
    # instrumented readonly transport? Can we do an instrumented
 
252
    # adapter and use self.get_readonly_url ?
 
253
 
 
254
    def test_fetch(self):
 
255
        #highest indices a: 5, b: 7
 
256
        br_a, br_b = make_branches(self)
 
257
        br_rem_a = Branch.open(self.get_readonly_url('branch1'))
 
258
        fetch_steps(self, br_rem_a, br_b, br_a)
 
259
 
 
260
    def _count_log_matches(self, target, logs):
 
261
        """Count the number of times the target file pattern was fetched in an http log"""
 
262
        get_succeeds_re = re.compile(
 
263
            '.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
 
264
            (     target,                    bzrlib.__version__))
 
265
        c = 0
 
266
        for line in logs:
 
267
            if get_succeeds_re.match(line):
 
268
                c += 1
 
269
        return c
 
270
 
 
271
    def test_weaves_are_retrieved_once(self):
 
272
        self.build_tree(("source/", "source/file", "target/"))
 
273
        # This test depends on knit dasta storage.
 
274
        wt = self.make_branch_and_tree('source', format='dirstate-tags')
 
275
        branch = wt.branch
 
276
        wt.add(["file"], ["id"])
 
277
        wt.commit("added file")
 
278
        open("source/file", 'w').write("blah\n")
 
279
        wt.commit("changed file")
 
280
        target = BzrDir.create_branch_and_repo("target/")
 
281
        source = Branch.open(self.get_readonly_url("source/"))
 
282
        self.assertEqual(target.fetch(source), (2, []))
 
283
        # this is the path to the literal file. As format changes 
 
284
        # occur it needs to be updated. FIXME: ask the store for the
 
285
        # path.
 
286
        self.log("web server logs are:")
 
287
        http_logs = self.get_readonly_server().logs
 
288
        self.log('\n'.join(http_logs))
 
289
        # unfortunately this log entry is branch format specific. We could 
 
290
        # factor out the 'what files does this format use' to a method on the 
 
291
        # repository, which would let us to this generically. RBC 20060419
 
292
        # RBC 20080408: Or perhaps we can assert that no files are fully read
 
293
        # twice?
 
294
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
 
295
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
 
296
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
 
297
        # this r-h check test will prevent regressions, but it currently already 
 
298
        # passes, before the patch to cache-rh is applied :[
 
299
        self.assertTrue(1 >= self._count_log_matches('revision-history',
 
300
                                                     http_logs))
 
301
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
302
                                                     http_logs))
 
303
        # FIXME naughty poking in there.
 
304
        self.get_readonly_server().logs = []
 
305
        # check there is nothing more to fetch.  We take care to re-use the
 
306
        # existing transport so that the request logs we're about to examine
 
307
        # aren't cluttered with redundant probes for a smart server.
 
308
        # XXX: Perhaps this further parameterisation: test http with smart
 
309
        # server, and test http without smart server?
 
310
        source = Branch.open(
 
311
            self.get_readonly_url("source/"),
 
312
            possible_transports=[source.bzrdir.root_transport])
 
313
        self.assertEqual(target.fetch(source), (0, []))
 
314
        # should make just two requests
 
315
        http_logs = self.get_readonly_server().logs
 
316
        self.log("web server logs are:")
 
317
        self.log('\n'.join(http_logs))
 
318
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
 
319
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
 
320
        self.assertEqual(1, self._count_log_matches('repository/format',
 
321
            http_logs))
 
322
        self.assertTrue(1 >= self._count_log_matches('revision-history',
 
323
                                                     http_logs))
 
324
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
325
                                                     http_logs))
 
326
        self.assertEqual(4, len(http_logs))
443
327
 
444
328
 
445
329
class Test1To2Fetch(TestCaseWithTransport):
491
375
        self.repo.fetch(self.tree.branch.repository, 'second-id')
492
376
        root_id = self.tree.get_root_id()
493
377
        self.assertEqual(
494
 
            ((root_id, 'left-parent'), (root_id, 'not-ghost-parent')),
 
378
            ((root_id, 'left-parent'), (root_id, 'ghost-parent'),
 
379
             (root_id, 'not-ghost-parent')),
495
380
            self.get_parents(root_id, 'second-id'))
496
381
 
497
382
    def make_two_commits(self, change_root, fetch_twice):