~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, 2010 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
16
 
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
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.test_revision import make_branches
35
 
from bzrlib.trace import mutter
36
 
from bzrlib.upgrade import Convert
37
 
from bzrlib.workingtree import WorkingTree
38
 
 
39
 
# These tests are a bit old; please instead add new tests into
40
 
# per_interrepository/ so they'll run on all relevant
41
 
# combinations.
42
 
 
43
 
 
44
 
def has_revision(branch, revision_id):
45
 
    return branch.repository.has_revision(revision_id)
46
 
 
47
 
def fetch_steps(self, br_a, br_b, writable_a):
48
 
    """A foreign test method for testing fetch locally and remotely."""
49
 
 
50
 
    # TODO RBC 20060201 make this a repository test.
51
 
    repo_b = br_b.repository
52
 
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
53
 
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
54
 
    self.assertEquals(len(br_b.revision_history()), 7)
55
 
    br_b.fetch(br_a, br_a.revision_history()[2])
56
 
    # branch.fetch is not supposed to alter the revision history
57
 
    self.assertEquals(len(br_b.revision_history()), 7)
58
 
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
59
 
 
60
 
    # fetching the next revision up in sample data copies one revision
61
 
    br_b.fetch(br_a, br_a.revision_history()[3])
62
 
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
63
 
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
64
 
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
65
 
 
66
 
    # When a non-branch ancestor is missing, it should be unlisted...
67
 
    # as its not reference from the inventory weave.
68
 
    br_b4 = self.make_branch('br_4')
69
 
    br_b4.fetch(br_b)
70
 
 
71
 
    writable_a.fetch(br_b)
72
 
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
73
 
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
74
 
 
75
 
    br_b2 = self.make_branch('br_b2')
76
 
    br_b2.fetch(br_b)
77
 
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
78
 
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
79
 
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
80
 
 
81
 
    br_a2 = self.make_branch('br_a2')
82
 
    br_a2.fetch(br_a)
83
 
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
84
 
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
85
 
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
86
 
 
87
 
    br_a3 = self.make_branch('br_a3')
88
 
    # pulling a branch with no revisions grabs nothing, regardless of
89
 
    # whats in the inventory.
90
 
    br_a3.fetch(br_a2)
91
 
    for revno in range(4):
92
 
        self.assertFalse(
93
 
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
94
 
    br_a3.fetch(br_a2, br_a.revision_history()[2])
95
 
    # 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
98
 
    # that was requested.
99
 
    self.assertRaises(errors.NoSuchRevision, br_a3.fetch, br_a2, 'pizza')
100
 
 
101
 
    # TODO: Test trying to fetch from a branch that points to a revision not
102
 
    # actually present in its repository.  Not every branch format allows you
103
 
    # to directly point to such revisions, so it's a bit complicated to
104
 
    # construct.  One way would be to uncommit and gc the revision, but not
105
 
    # every branch supports that.  -- mbp 20070814
106
 
 
107
 
    #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
109
 
    # add the right parents.
110
 
 
111
 
 
112
 
class TestFetch(TestCaseWithTransport):
113
 
 
114
 
    def test_fetch(self):
 
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
            
115
34
        #highest indices a: 5, b: 7
116
 
        br_a, br_b = make_branches(self, format='dirstate-tags')
117
 
        fetch_steps(self, br_a, br_b, br_a)
118
 
 
119
 
    def test_fetch_self(self):
120
 
        wt = self.make_branch_and_tree('br')
121
 
        wt.branch.fetch(wt.branch)
122
 
 
123
 
    def test_fetch_root_knit(self):
124
 
        """Ensure that knit2.fetch() updates the root knit
125
 
 
126
 
        This tests the case where the root has a new revision, but there are no
127
 
        corresponding filename, parent, contents or other changes.
128
 
        """
129
 
        knit1_format = bzrdir.BzrDirMetaFormat1()
130
 
        knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
131
 
        knit2_format = bzrdir.BzrDirMetaFormat1()
132
 
        knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
133
 
        # we start with a knit1 repository because that causes the
134
 
        # root revision to change for each commit, even though the content,
135
 
        # parent, name, and other attributes are unchanged.
136
 
        tree = self.make_branch_and_tree('tree', knit1_format)
137
 
        tree.set_root_id('tree-root')
138
 
        tree.commit('rev1', rev_id='rev1')
139
 
        tree.commit('rev2', rev_id='rev2')
140
 
 
141
 
        # Now we convert it to a knit2 repository so that it has a root knit
142
 
        Convert(tree.basedir, knit2_format)
143
 
        tree = WorkingTree.open(tree.basedir)
144
 
        branch = self.make_branch('branch', format=knit2_format)
145
 
        branch.pull(tree.branch, stop_revision='rev1')
146
 
        repo = branch.repository
147
 
        repo.lock_read()
148
 
        try:
149
 
            # Make sure fetch retrieved only what we requested
150
 
            self.assertEqual({('tree-root', 'rev1'):()},
151
 
                repo.texts.get_parent_map(
152
 
                    [('tree-root', 'rev1'), ('tree-root', 'rev2')]))
153
 
        finally:
154
 
            repo.unlock()
155
 
        branch.pull(tree.branch)
156
 
        # Make sure that the next revision in the root knit was retrieved,
157
 
        # even though the text, name, parent_id, etc., were unchanged.
158
 
        repo.lock_read()
159
 
        try:
160
 
            # Make sure fetch retrieved only what we requested
161
 
            self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
162
 
                repo.texts.get_parent_map([('tree-root', 'rev2')]))
163
 
        finally:
164
 
            repo.unlock()
165
 
 
166
 
    def test_fetch_incompatible(self):
167
 
        knit_tree = self.make_branch_and_tree('knit', format='knit')
168
 
        knit3_tree = self.make_branch_and_tree('knit3',
169
 
            format='dirstate-with-subtree')
170
 
        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")
176
 
 
177
 
 
178
 
class TestMergeFetch(TestCaseWithTransport):
179
 
 
180
 
    def test_merge_fetches_unrelated(self):
181
 
        """Merge brings across history from unrelated source"""
182
 
        wt1 = self.make_branch_and_tree('br1')
183
 
        br1 = wt1.branch
184
 
        wt1.commit(message='rev 1-1', rev_id='1-1')
185
 
        wt1.commit(message='rev 1-2', rev_id='1-2')
186
 
        wt2 = self.make_branch_and_tree('br2')
187
 
        br2 = wt2.branch
188
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
189
 
        wt2.merge_from_branch(br1, from_revision='null:')
190
 
        self._check_revs_present(br2)
191
 
 
192
 
    def test_merge_fetches(self):
193
 
        """Merge brings across history from source"""
194
 
        wt1 = self.make_branch_and_tree('br1')
195
 
        br1 = wt1.branch
196
 
        wt1.commit(message='rev 1-1', rev_id='1-1')
197
 
        dir_2 = br1.bzrdir.sprout('br2')
198
 
        br2 = dir_2.open_branch()
199
 
        wt1.commit(message='rev 1-2', rev_id='1-2')
200
 
        wt2 = dir_2.open_workingtree()
201
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
202
 
        wt2.merge_from_branch(br1)
203
 
        self._check_revs_present(br2)
204
 
 
205
 
    def _check_revs_present(self, br2):
206
 
        for rev_id in '1-1', '1-2', '2-1':
207
 
            self.assertTrue(br2.repository.has_revision(rev_id))
208
 
            rev = br2.repository.get_revision(rev_id)
209
 
            self.assertEqual(rev.revision_id, rev_id)
210
 
            self.assertTrue(br2.repository.get_inventory(rev_id))
211
 
 
212
 
 
213
 
class TestMergeFileHistory(TestCaseWithTransport):
214
 
 
215
 
    def setUp(self):
216
 
        super(TestMergeFileHistory, self).setUp()
217
 
        wt1 = self.make_branch_and_tree('br1')
218
 
        br1 = wt1.branch
219
 
        self.build_tree_contents([('br1/file', 'original contents\n')])
220
 
        wt1.add('file', 'this-file-id')
221
 
        wt1.commit(message='rev 1-1', rev_id='1-1')
222
 
        dir_2 = br1.bzrdir.sprout('br2')
223
 
        br2 = dir_2.open_branch()
224
 
        wt2 = dir_2.open_workingtree()
225
 
        self.build_tree_contents([('br1/file', 'original from 1\n')])
226
 
        wt1.commit(message='rev 1-2', rev_id='1-2')
227
 
        self.build_tree_contents([('br1/file', 'agreement\n')])
228
 
        wt1.commit(message='rev 1-3', rev_id='1-3')
229
 
        self.build_tree_contents([('br2/file', 'contents in 2\n')])
230
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
231
 
        self.build_tree_contents([('br2/file', 'agreement\n')])
232
 
        wt2.commit(message='rev 2-2', rev_id='2-2')
233
 
 
234
 
    def test_merge_fetches_file_history(self):
235
 
        """Merge brings across file histories"""
236
 
        br2 = Branch.open('br2')
237
 
        br1 = Branch.open('br1')
238
 
        wt2 = WorkingTree.open('br2').merge_from_branch(br1)
239
 
        br2.lock_read()
240
 
        self.addCleanup(br2.unlock)
241
 
        for rev_id, text in [('1-2', 'original from 1\n'),
242
 
                             ('1-3', 'agreement\n'),
243
 
                             ('2-1', 'contents in 2\n'),
244
 
                             ('2-2', 'agreement\n')]:
245
 
            self.assertEqualDiff(
246
 
                br2.repository.revision_tree(
247
 
                    rev_id).get_file_text('this-file-id'), text)
248
 
 
249
 
 
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)
443
 
 
444
 
 
445
 
class Test1To2Fetch(TestCaseWithTransport):
446
 
    """Tests for Model1To2 failure modes"""
447
 
 
448
 
    def make_tree_and_repo(self):
449
 
        self.tree = self.make_branch_and_tree('tree', format='pack-0.92')
450
 
        self.repo = self.make_repository('rich-repo', format='rich-root-pack')
451
 
        self.repo.lock_write()
452
 
        self.addCleanup(self.repo.unlock)
453
 
 
454
 
    def do_fetch_order_test(self, first, second):
455
 
        """Test that fetch works no matter what the set order of revision is.
456
 
 
457
 
        This test depends on the order of items in a set, which is
458
 
        implementation-dependant, so we test A, B and then B, A.
459
 
        """
460
 
        self.make_tree_and_repo()
461
 
        self.tree.commit('Commit 1', rev_id=first)
462
 
        self.tree.commit('Commit 2', rev_id=second)
463
 
        self.repo.fetch(self.tree.branch.repository, second)
464
 
 
465
 
    def test_fetch_order_AB(self):
466
 
        """See do_fetch_order_test"""
467
 
        self.do_fetch_order_test('A', 'B')
468
 
 
469
 
    def test_fetch_order_BA(self):
470
 
        """See do_fetch_order_test"""
471
 
        self.do_fetch_order_test('B', 'A')
472
 
 
473
 
    def get_parents(self, file_id, revision_id):
474
 
        self.repo.lock_read()
475
 
        try:
476
 
            parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
477
 
            return parent_map[(file_id, revision_id)]
478
 
        finally:
479
 
            self.repo.unlock()
480
 
 
481
 
    def test_fetch_ghosts(self):
482
 
        self.make_tree_and_repo()
483
 
        self.tree.commit('first commit', rev_id='left-parent')
484
 
        self.tree.add_parent_tree_id('ghost-parent')
485
 
        fork = self.tree.bzrdir.sprout('fork', 'null:').open_workingtree()
486
 
        fork.commit('not a ghost', rev_id='not-ghost-parent')
487
 
        self.tree.branch.repository.fetch(fork.branch.repository,
488
 
                                     'not-ghost-parent')
489
 
        self.tree.add_parent_tree_id('not-ghost-parent')
490
 
        self.tree.commit('second commit', rev_id='second-id')
491
 
        self.repo.fetch(self.tree.branch.repository, 'second-id')
492
 
        root_id = self.tree.get_root_id()
493
 
        self.assertEqual(
494
 
            ((root_id, 'left-parent'), (root_id, 'not-ghost-parent')),
495
 
            self.get_parents(root_id, 'second-id'))
496
 
 
497
 
    def make_two_commits(self, change_root, fetch_twice):
498
 
        self.make_tree_and_repo()
499
 
        self.tree.commit('first commit', rev_id='first-id')
500
 
        if change_root:
501
 
            self.tree.set_root_id('unique-id')
502
 
        self.tree.commit('second commit', rev_id='second-id')
503
 
        if fetch_twice:
504
 
            self.repo.fetch(self.tree.branch.repository, 'first-id')
505
 
        self.repo.fetch(self.tree.branch.repository, 'second-id')
506
 
 
507
 
    def test_fetch_changed_root(self):
508
 
        self.make_two_commits(change_root=True, fetch_twice=False)
509
 
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
510
 
 
511
 
    def test_two_fetch_changed_root(self):
512
 
        self.make_two_commits(change_root=True, fetch_twice=True)
513
 
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
514
 
 
515
 
    def test_two_fetches(self):
516
 
        self.make_two_commits(change_root=False, fetch_twice=True)
517
 
        self.assertEqual((('TREE_ROOT', 'first-id'),),
518
 
            self.get_parents('TREE_ROOT', 'second-id'))
 
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()))