~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

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