/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: John Arbash Meinel
  • Date: 2006-04-25 15:05:42 UTC
  • mfrom: (1185.85.85 bzr-encoding)
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: john@arbash-meinel.com-20060425150542-c7b518dca9928691
[merge] the old bzr-encoding changes, reparenting them on bzr.dev

Show diffs side-by-side

added added

removed removed

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