~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: Aaron Bentley
  • Date: 2006-06-21 14:30:57 UTC
  • mfrom: (1801.1.1 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 1803.
  • Revision ID: abentley@panoramicfeedback.com-20060621143057-776e4b8d707e430e
Install benchmarks. (Jelmer Vernooij)

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