~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Andrew Bennetts
  • Date: 2011-01-06 06:26:23 UTC
  • mto: This revision was merged to the branch mainline in revision 5612.
  • Revision ID: andrew.bennetts@canonical.com-20110106062623-43tda58mqroybjui
Start of a developer doc describing how fetch works.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
    branch,
22
22
    bzrdir,
23
23
    config,
 
24
    controldir,
24
25
    debug,
25
26
    errors,
26
27
    graph,
32
33
    revision as _mod_revision,
33
34
    static_tuple,
34
35
    symbol_versioning,
 
36
    urlutils,
35
37
)
36
38
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
37
39
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
213
215
        if len(branch_info) != 2:
214
216
            raise errors.UnexpectedSmartServerResponse(response)
215
217
        branch_ref, branch_name = branch_info
216
 
        format = bzrdir.network_format_registry.get(control_name)
 
218
        format = controldir.network_format_registry.get(control_name)
217
219
        if repo_name:
218
220
            format.repository_format = repository.network_format_registry.get(
219
221
                repo_name)
245
247
        self._ensure_real()
246
248
        self._real_bzrdir.destroy_repository()
247
249
 
248
 
    def create_branch(self, name=None):
 
250
    def create_branch(self, name=None, repository=None):
249
251
        # as per meta1 formats - just delegate to the format object which may
250
252
        # be parameterised.
251
253
        real_branch = self._format.get_branch_format().initialize(self,
252
 
            name=name)
 
254
            name=name, repository=repository)
253
255
        if not isinstance(real_branch, RemoteBranch):
254
 
            result = RemoteBranch(self, self.find_repository(), real_branch,
255
 
                                  name=name)
 
256
            if not isinstance(repository, RemoteRepository):
 
257
                raise AssertionError(
 
258
                    'need a RemoteRepository to use with RemoteBranch, got %r'
 
259
                    % (repository,))
 
260
            result = RemoteBranch(self, repository, real_branch, name=name)
256
261
        else:
257
262
            result = real_branch
258
263
        # BzrDir.clone_on_transport() uses the result of create_branch but does
270
275
        self._real_bzrdir.destroy_branch(name=name)
271
276
        self._next_open_branch_result = None
272
277
 
273
 
    def create_workingtree(self, revision_id=None, from_branch=None):
 
278
    def create_workingtree(self, revision_id=None, from_branch=None,
 
279
        accelerator_tree=None, hardlink=False):
274
280
        raise errors.NotLocalUrl(self.transport.base)
275
281
 
276
 
    def find_branch_format(self):
 
282
    def find_branch_format(self, name=None):
277
283
        """Find the branch 'format' for this bzrdir.
278
284
 
279
285
        This might be a synthetic object for e.g. RemoteBranch and SVN.
280
286
        """
281
 
        b = self.open_branch()
 
287
        b = self.open_branch(name=name)
282
288
        return b._format
283
289
 
284
 
    def get_branch_reference(self):
 
290
    def get_branch_reference(self, name=None):
285
291
        """See BzrDir.get_branch_reference()."""
 
292
        if name is not None:
 
293
            # XXX JRV20100304: Support opening colocated branches
 
294
            raise errors.NoColocatedBranchSupport(self)
286
295
        response = self._get_branch_reference()
287
296
        if response[0] == 'ref':
288
297
            return response[1]
319
328
            raise errors.UnexpectedSmartServerResponse(response)
320
329
        return response
321
330
 
322
 
    def _get_tree_branch(self):
 
331
    def _get_tree_branch(self, name=None):
323
332
        """See BzrDir._get_tree_branch()."""
324
 
        return None, self.open_branch()
 
333
        return None, self.open_branch(name=name)
325
334
 
326
335
    def open_branch(self, name=None, unsupported=False,
327
336
                    ignore_fallbacks=False):
645
654
 
646
655
 
647
656
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
648
 
    bzrdir.ControlComponent):
 
657
    controldir.ControlComponent):
649
658
    """Repository accessed over rpc.
650
659
 
651
660
    For the moment most operations are performed using local transport-backed
896
905
    def _has_same_fallbacks(self, other_repo):
897
906
        """Returns true if the repositories have the same fallbacks."""
898
907
        # XXX: copied from Repository; it should be unified into a base class
899
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
 
908
        # <https://bugs.launchpad.net/bzr/+bug/401622>
900
909
        my_fb = self._fallback_repositories
901
910
        other_fb = other_repo._fallback_repositories
902
911
        if len(my_fb) != len(other_fb):
1000
1009
    def lock_read(self):
1001
1010
        """Lock the repository for read operations.
1002
1011
 
1003
 
        :return: An object with an unlock method which will release the lock
1004
 
            obtained.
 
1012
        :return: A bzrlib.lock.LogicalLockResult.
1005
1013
        """
1006
1014
        # wrong eventually - want a local lock cache context
1007
1015
        if not self._lock_mode:
1015
1023
                repo.lock_read()
1016
1024
        else:
1017
1025
            self._lock_count += 1
1018
 
        return self
 
1026
        return lock.LogicalLockResult(self.unlock)
1019
1027
 
1020
1028
    def _remote_lock_write(self, token):
1021
1029
        path = self.bzrdir._path_for_remote_call(self._client)
1313
1321
        return self._real_repository.make_working_trees()
1314
1322
 
1315
1323
    def refresh_data(self):
1316
 
        """Re-read any data needed to to synchronise with disk.
 
1324
        """Re-read any data needed to synchronise with disk.
1317
1325
 
1318
1326
        This method is intended to be called after another repository instance
1319
1327
        (such as one used by a smart server) has inserted data into the
1320
 
        repository. It may not be called during a write group, but may be
1321
 
        called at any other time.
 
1328
        repository. On all repositories this will work outside of write groups.
 
1329
        Some repository formats (pack and newer for bzrlib native formats)
 
1330
        support refresh_data inside write groups. If called inside a write
 
1331
        group on a repository that does not support refreshing in a write group
 
1332
        IsInWriteGroupError will be raised.
1322
1333
        """
1323
 
        if self.is_in_write_group():
1324
 
            raise errors.InternalBzrError(
1325
 
                "May not refresh_data while in a write group.")
1326
1334
        if self._real_repository is not None:
1327
1335
            self._real_repository.refresh_data()
1328
1336
 
1340
1348
        return result
1341
1349
 
1342
1350
    @needs_read_lock
1343
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1351
    def search_missing_revision_ids(self, other,
 
1352
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1353
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1344
1354
        """Return the revision ids that other has that this does not.
1345
1355
 
1346
1356
        These are returned in topological order.
1347
1357
 
1348
1358
        revision_id: only return revision ids included by revision_id.
1349
1359
        """
1350
 
        return repository.InterRepository.get(
1351
 
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
1360
        if symbol_versioning.deprecated_passed(revision_id):
 
1361
            symbol_versioning.warn(
 
1362
                'search_missing_revision_ids(revision_id=...) was '
 
1363
                'deprecated in 2.3.  Use revision_ids=[...] instead.',
 
1364
                DeprecationWarning, stacklevel=2)
 
1365
            if revision_ids is not None:
 
1366
                raise AssertionError(
 
1367
                    'revision_ids is mutually exclusive with revision_id')
 
1368
            if revision_id is not None:
 
1369
                revision_ids = [revision_id]
 
1370
        inter_repo = repository.InterRepository.get(other, self)
 
1371
        return inter_repo.search_missing_revision_ids(
 
1372
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1373
            if_present_ids=if_present_ids)
1352
1374
 
1353
1375
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1354
1376
            fetch_spec=None):
1755
1777
        return '\n'.join((start_keys, stop_keys, count))
1756
1778
 
1757
1779
    def _serialise_search_result(self, search_result):
1758
 
        if isinstance(search_result, graph.PendingAncestryResult):
1759
 
            parts = ['ancestry-of']
1760
 
            parts.extend(search_result.heads)
1761
 
        else:
1762
 
            recipe = search_result.get_recipe()
1763
 
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1780
        parts = search_result.get_network_struct()
1764
1781
        return '\n'.join(parts)
1765
1782
 
1766
1783
    def autopack(self):
1960
1977
        candidate_verbs = [
1961
1978
            ('Repository.get_stream_1.19', (1, 19)),
1962
1979
            ('Repository.get_stream', (1, 13))]
 
1980
 
1963
1981
        found_verb = False
1964
1982
        for verb, version in candidate_verbs:
1965
1983
            if medium._is_remote_before(version):
1969
1987
                    verb, args, search_bytes)
1970
1988
            except errors.UnknownSmartMethod:
1971
1989
                medium._remember_remote_is_before(version)
 
1990
            except errors.UnknownErrorFromSmartServer, e:
 
1991
                if isinstance(search, graph.EverythingResult):
 
1992
                    error_verb = e.error_from_smart_server.error_verb
 
1993
                    if error_verb == 'BadSearch':
 
1994
                        # Pre-2.3 servers don't support this sort of search.
 
1995
                        # XXX: perhaps falling back to VFS on BadSearch is a
 
1996
                        # good idea in general?  It might provide a little bit
 
1997
                        # of protection against client-side bugs.
 
1998
                        medium._remember_remote_is_before((2, 3))
 
1999
                        break
 
2000
                raise
1972
2001
            else:
1973
2002
                response_tuple, response_handler = response
1974
2003
                found_verb = True
1978
2007
        if response_tuple[0] != 'ok':
1979
2008
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1980
2009
        byte_stream = response_handler.read_streamed_body()
1981
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
2010
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
2011
            self._record_counter)
1982
2012
        if src_format.network_name() != repo._format.network_name():
1983
2013
            raise AssertionError(
1984
2014
                "Mismatched RemoteRepository and stream src %r, %r" % (
2088
2118
                                  name=name)
2089
2119
        return result
2090
2120
 
2091
 
    def initialize(self, a_bzrdir, name=None):
 
2121
    def initialize(self, a_bzrdir, name=None, repository=None):
2092
2122
        # 1) get the network name to use.
2093
2123
        if self._custom_format:
2094
2124
            network_name = self._custom_format.network_name()
2122
2152
        # Turn the response into a RemoteRepository object.
2123
2153
        format = RemoteBranchFormat(network_name=response[1])
2124
2154
        repo_format = response_tuple_to_repo_format(response[3:])
2125
 
        if response[2] == '':
2126
 
            repo_bzrdir = a_bzrdir
 
2155
        repo_path = response[2]
 
2156
        if repository is not None:
 
2157
            remote_repo_url = urlutils.join(medium.base, repo_path)
 
2158
            url_diff = urlutils.relative_url(repository.user_url,
 
2159
                    remote_repo_url)
 
2160
            if url_diff != '.':
 
2161
                raise AssertionError(
 
2162
                    'repository.user_url %r does not match URL from server '
 
2163
                    'response (%r + %r)'
 
2164
                    % (repository.user_url, medium.base, repo_path))
 
2165
            remote_repo = repository
2127
2166
        else:
2128
 
            repo_bzrdir = RemoteBzrDir(
2129
 
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2130
 
                a_bzrdir._client)
2131
 
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2167
            if repo_path == '':
 
2168
                repo_bzrdir = a_bzrdir
 
2169
            else:
 
2170
                repo_bzrdir = RemoteBzrDir(
 
2171
                    a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
 
2172
                    a_bzrdir._client)
 
2173
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2132
2174
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2133
2175
            format=format, setup_stacking=False, name=name)
2134
2176
        # XXX: We know this is a new branch, so it must have revno 0, revid
2364
2406
        self._ensure_real()
2365
2407
        return self._real_branch._get_tags_bytes()
2366
2408
 
 
2409
    @needs_read_lock
2367
2410
    def _get_tags_bytes(self):
 
2411
        if self._tags_bytes is None:
 
2412
            self._tags_bytes = self._get_tags_bytes_via_hpss()
 
2413
        return self._tags_bytes
 
2414
 
 
2415
    def _get_tags_bytes_via_hpss(self):
2368
2416
        medium = self._client._medium
2369
2417
        if medium._is_remote_before((1, 13)):
2370
2418
            return self._vfs_get_tags_bytes()
2380
2428
        return self._real_branch._set_tags_bytes(bytes)
2381
2429
 
2382
2430
    def _set_tags_bytes(self, bytes):
 
2431
        if self.is_locked():
 
2432
            self._tags_bytes = bytes
2383
2433
        medium = self._client._medium
2384
2434
        if medium._is_remote_before((1, 18)):
2385
2435
            self._vfs_set_tags_bytes(bytes)
2396
2446
    def lock_read(self):
2397
2447
        """Lock the branch for read operations.
2398
2448
 
2399
 
        :return: An object with an unlock method which will release the lock
2400
 
            obtained.
 
2449
        :return: A bzrlib.lock.LogicalLockResult.
2401
2450
        """
2402
2451
        self.repository.lock_read()
2403
2452
        if not self._lock_mode:
2408
2457
                self._real_branch.lock_read()
2409
2458
        else:
2410
2459
            self._lock_count += 1
2411
 
        return self
 
2460
        return lock.LogicalLockResult(self.unlock)
2412
2461
 
2413
2462
    def _remote_lock_write(self, token):
2414
2463
        if token is None:
2418
2467
            repo_token = self.repository.lock_write().repository_token
2419
2468
            self.repository.unlock()
2420
2469
        err_context = {'token': token}
2421
 
        response = self._call(
2422
 
            'Branch.lock_write', self._remote_path(), branch_token,
2423
 
            repo_token or '', **err_context)
 
2470
        try:
 
2471
            response = self._call(
 
2472
                'Branch.lock_write', self._remote_path(), branch_token,
 
2473
                repo_token or '', **err_context)
 
2474
        except errors.LockContention, e:
 
2475
            # The LockContention from the server doesn't have any
 
2476
            # information about the lock_url. We re-raise LockContention
 
2477
            # with valid lock_url.
 
2478
            raise errors.LockContention('(remote lock)',
 
2479
                self.repository.base.split('.bzr/')[0])
2424
2480
        if response[0] != 'ok':
2425
2481
            raise errors.UnexpectedSmartServerResponse(response)
2426
2482
        ok, branch_token, repo_token = response
2447
2503
            self._lock_mode = 'w'
2448
2504
            self._lock_count = 1
2449
2505
        elif self._lock_mode == 'r':
2450
 
            raise errors.ReadOnlyTransaction
 
2506
            raise errors.ReadOnlyError(self)
2451
2507
        else:
2452
2508
            if token is not None:
2453
2509
                # A token was given to lock_write, and we're relocking, so
2787
2843
        medium = self._branch._client._medium
2788
2844
        if medium._is_remote_before((1, 14)):
2789
2845
            return self._vfs_set_option(value, name, section)
 
2846
        if isinstance(value, dict):
 
2847
            if medium._is_remote_before((2, 2)):
 
2848
                return self._vfs_set_option(value, name, section)
 
2849
            return self._set_config_option_dict(value, name, section)
 
2850
        else:
 
2851
            return self._set_config_option(value, name, section)
 
2852
 
 
2853
    def _set_config_option(self, value, name, section):
2790
2854
        try:
2791
2855
            path = self._branch._remote_path()
2792
2856
            response = self._branch._client.call('Branch.set_config_option',
2793
2857
                path, self._branch._lock_token, self._branch._repo_lock_token,
2794
2858
                value.encode('utf8'), name, section or '')
2795
2859
        except errors.UnknownSmartMethod:
 
2860
            medium = self._branch._client._medium
2796
2861
            medium._remember_remote_is_before((1, 14))
2797
2862
            return self._vfs_set_option(value, name, section)
2798
2863
        if response != ():
2799
2864
            raise errors.UnexpectedSmartServerResponse(response)
2800
2865
 
 
2866
    def _serialize_option_dict(self, option_dict):
 
2867
        utf8_dict = {}
 
2868
        for key, value in option_dict.items():
 
2869
            if isinstance(key, unicode):
 
2870
                key = key.encode('utf8')
 
2871
            if isinstance(value, unicode):
 
2872
                value = value.encode('utf8')
 
2873
            utf8_dict[key] = value
 
2874
        return bencode.bencode(utf8_dict)
 
2875
 
 
2876
    def _set_config_option_dict(self, value, name, section):
 
2877
        try:
 
2878
            path = self._branch._remote_path()
 
2879
            serialised_dict = self._serialize_option_dict(value)
 
2880
            response = self._branch._client.call(
 
2881
                'Branch.set_config_option_dict',
 
2882
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2883
                serialised_dict, name, section or '')
 
2884
        except errors.UnknownSmartMethod:
 
2885
            medium = self._branch._client._medium
 
2886
            medium._remember_remote_is_before((2, 2))
 
2887
            return self._vfs_set_option(value, name, section)
 
2888
        if response != ():
 
2889
            raise errors.UnexpectedSmartServerResponse(response)
 
2890
 
2801
2891
    def _real_object(self):
2802
2892
        self._branch._ensure_real()
2803
2893
        return self._branch._real_branch