/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 remote.py

Fix more tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2008 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Jelmer Vernooij <jelmer@samba.org>
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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
import bzrlib
18
 
from bzrlib import urlutils
19
 
from bzrlib.bzrdir import BzrDir, BzrDirFormat
20
 
from bzrlib.errors import NoSuchFile, NotLocalUrl
21
 
from bzrlib.lockable_files import TransportLock
22
 
from bzrlib.repository import Repository
23
 
from bzrlib.trace import info
24
 
from bzrlib.transport import Transport
25
 
 
26
 
from bzrlib.plugins.git import git
27
 
from bzrlib.plugins.git.branch import GitBranch
28
 
from bzrlib.plugins.git.dir import GitDir
29
 
from bzrlib.plugins.git.foreign import ForeignBranch
30
 
from bzrlib.plugins.git.repository import GitFormat, GitRepository
31
 
 
 
17
from bzrlib import (
 
18
    config,
 
19
    debug,
 
20
    trace,
 
21
    ui,
 
22
    urlutils,
 
23
    )
 
24
from bzrlib.errors import (
 
25
    BzrError,
 
26
    InvalidRevisionId,
 
27
    NoSuchFile,
 
28
    NoSuchRevision,
 
29
    NotBranchError,
 
30
    NotLocalUrl,
 
31
    UninitializableFormat,
 
32
    )
 
33
from bzrlib.transport import (
 
34
    Transport,
 
35
    )
 
36
 
 
37
from bzrlib.plugins.git import (
 
38
    lazy_check_versions,
 
39
    )
 
40
lazy_check_versions()
 
41
 
 
42
from bzrlib.plugins.git.branch import (
 
43
    GitBranch,
 
44
    GitTags,
 
45
    )
 
46
from bzrlib.plugins.git.dir import (
 
47
    GitControlDirFormat,
 
48
    GitDir,
 
49
    GitLockableFiles,
 
50
    GitLock,
 
51
    )
 
52
from bzrlib.plugins.git.errors import (
 
53
    GitSmartRemoteNotSupported,
 
54
    NoSuchRef,
 
55
    )
 
56
from bzrlib.plugins.git.mapping import (
 
57
    mapping_registry,
 
58
    )
 
59
from bzrlib.plugins.git.repository import (
 
60
    GitRepository,
 
61
    )
 
62
from bzrlib.plugins.git.refs import (
 
63
    extract_tags,
 
64
    branch_name_to_ref,
 
65
    )
 
66
 
 
67
import dulwich as git
 
68
from dulwich.errors import (
 
69
    GitProtocolError,
 
70
    )
 
71
from dulwich.pack import (
 
72
    Pack,
 
73
    PackData,
 
74
    )
 
75
import os
 
76
import tempfile
32
77
import urllib
33
78
import urlparse
34
 
 
35
 
from dulwich.pack import PackData
 
79
urlparse.uses_netloc.extend(['git', 'git+ssh'])
 
80
 
 
81
from dulwich.pack import load_pack_index
 
82
 
 
83
 
 
84
# Don't run any tests on GitSmartTransport as it is not intended to be
 
85
# a full implementation of Transport
 
86
def get_test_permutations():
 
87
    return []
 
88
 
 
89
 
 
90
def split_git_url(url):
 
91
    """Split a Git URL.
 
92
 
 
93
    :param url: Git URL
 
94
    :return: Tuple with host, port, username, path.
 
95
    """
 
96
    (scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
 
97
    path = urllib.unquote(loc)
 
98
    if path.startswith("/~"):
 
99
        path = path[1:]
 
100
    (username, hostport) = urllib.splituser(netloc)
 
101
    (host, port) = urllib.splitnport(hostport, None)
 
102
    return (host, port, username, path)
 
103
 
 
104
 
 
105
def parse_git_error(url, message):
 
106
    """Parse a remote git server error and return a bzr exception.
 
107
 
 
108
    :param url: URL of the remote repository
 
109
    :param message: Message sent by the remote git server
 
110
    """
 
111
    message = str(message).strip()
 
112
    if message.startswith("Could not find Repository "):
 
113
        return NotBranchError(url, message)
 
114
    # Don't know, just return it to the user as-is
 
115
    return BzrError(message)
36
116
 
37
117
 
38
118
class GitSmartTransport(Transport):
39
119
 
40
120
    def __init__(self, url, _client=None):
41
121
        Transport.__init__(self, url)
42
 
        (scheme, _, loc, _, _) = urlparse.urlsplit(url)
43
 
        assert scheme == "git"
44
 
        hostport, self._path = urllib.splithost(loc)
45
 
        (self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
46
 
        if _client is not None:
47
 
            self._client = _client
48
 
        else:
49
 
            self._client = git.client.TCPGitClient(self._host, self._port)
 
122
        (self._host, self._port, self._username, self._path) = \
 
123
            split_git_url(url)
 
124
        if 'transport' in debug.debug_flags:
 
125
            trace.mutter('host: %r, user: %r, port: %r, path: %r',
 
126
                         self._host, self._username, self._port, self._path)
 
127
        self._client = _client
 
128
 
 
129
    def external_url(self):
 
130
        return self.base
 
131
 
 
132
    def has(self, relpath):
 
133
        return False
 
134
 
 
135
    def _get_client(self, thin_packs):
 
136
        raise NotImplementedError(self._get_client)
 
137
 
 
138
    def _get_path(self):
 
139
        return urlutils.split_segment_parameters_raw(self._path)[0]
50
140
 
51
141
    def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
52
142
        if progress is None:
53
143
            def progress(text):
54
 
                info("git: %s" % text)
55
 
        self._client.fetch_pack(self._path, determine_wants, graph_walker, 
56
 
                pack_data, progress)
 
144
                trace.info("git: %s" % text)
 
145
        client = self._get_client(thin_packs=False)
 
146
        try:
 
147
            return client.fetch_pack(self._get_path(), determine_wants,
 
148
                graph_walker, pack_data, progress)
 
149
        except GitProtocolError, e:
 
150
            raise parse_git_error(self.external_url(), e)
57
151
 
58
 
    def fetch_objects(self, determine_wants, graph_walker, progress=None):
59
 
        fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
60
 
        self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
61
 
        os.close(fd)
 
152
    def send_pack(self, get_changed_refs, generate_pack_contents):
 
153
        client = self._get_client(thin_packs=False)
62
154
        try:
63
 
            p = PackData(path)
64
 
            for o in p.iterobjects():
65
 
                yield o
66
 
        finally:
67
 
            os.remove(path)
 
155
            return client.send_pack(self._get_path(), get_changed_refs,
 
156
                generate_pack_contents)
 
157
        except GitProtocolError, e:
 
158
            raise parse_git_error(self.external_url(), e)
68
159
 
69
160
    def get(self, path):
70
161
        raise NoSuchFile(path)
71
162
 
 
163
    def abspath(self, relpath):
 
164
        return urlutils.join(self.base, relpath)
 
165
 
72
166
    def clone(self, offset=None):
73
167
        """See Transport.clone()."""
74
168
        if offset is None:
76
170
        else:
77
171
            newurl = urlutils.join(self.base, offset)
78
172
 
79
 
        return GitSmartTransport(newurl, self._client)
 
173
        return self.__class__(newurl, self._client)
 
174
 
 
175
 
 
176
class TCPGitSmartTransport(GitSmartTransport):
 
177
 
 
178
    _scheme = 'git'
 
179
 
 
180
    def _get_client(self, thin_packs):
 
181
        if self._client is not None:
 
182
            ret = self._client
 
183
            self._client = None
 
184
            return ret
 
185
        return git.client.TCPGitClient(self._host, self._port,
 
186
            thin_packs=thin_packs, report_activity=self._report_activity)
 
187
 
 
188
 
 
189
class SSHGitSmartTransport(GitSmartTransport):
 
190
 
 
191
    _scheme = 'git+ssh'
 
192
 
 
193
    def _get_path(self):
 
194
        path = urlutils.split_segment_parameters_raw(self._path)[0]
 
195
        if path.startswith("/~/"):
 
196
            return path[3:]
 
197
        return path
 
198
 
 
199
    def _get_client(self, thin_packs):
 
200
        if self._client is not None:
 
201
            ret = self._client
 
202
            self._client = None
 
203
            return ret
 
204
        location_config = config.LocationConfig(self.base)
 
205
        client = git.client.SSHGitClient(self._host, self._port, self._username,
 
206
            thin_packs=thin_packs, report_activity=self._report_activity)
 
207
        # Set up alternate pack program paths
 
208
        upload_pack = location_config.get_user_option('git_upload_pack')
 
209
        if upload_pack:
 
210
            client.alternative_paths["upload-pack"] = upload_pack
 
211
        receive_pack = location_config.get_user_option('git_receive_pack')
 
212
        if receive_pack:
 
213
            client.alternative_paths["receive-pack"] = receive_pack
 
214
        return client
80
215
 
81
216
 
82
217
class RemoteGitDir(GitDir):
86
221
        self.root_transport = transport
87
222
        self.transport = transport
88
223
        self._lockfiles = lockfiles
 
224
        self._mode_check_done = None
 
225
 
 
226
    @property
 
227
    def user_url(self):
 
228
        return self.control_url
 
229
 
 
230
    @property
 
231
    def user_transport(self):
 
232
        return self.root_transport
89
233
 
90
234
    def open_repository(self):
91
235
        return RemoteGitRepository(self, self._lockfiles)
92
236
 
93
 
    def open_branch(self):
 
237
    def open_branch(self, name=None, unsupported=False,
 
238
            ignore_fallbacks=False):
94
239
        repo = self.open_repository()
95
 
        # TODO: Support for multiple branches in one bzrdir in bzrlib!
96
 
        return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
 
240
        refname = self._get_selected_ref(name)
 
241
        return RemoteGitBranch(self, repo, refname, self._lockfiles)
97
242
 
98
 
    def open_workingtree(self):
 
243
    def open_workingtree(self, recommend_upgrade=False):
99
244
        raise NotLocalUrl(self.transport.base)
100
245
 
101
246
 
 
247
class EmptyObjectStoreIterator(dict):
 
248
 
 
249
    def iterobjects(self):
 
250
        return []
 
251
 
 
252
 
 
253
class TemporaryPackIterator(Pack):
 
254
 
 
255
    def __init__(self, path, resolve_ext_ref):
 
256
        super(TemporaryPackIterator, self).__init__(path)
 
257
        self.resolve_ext_ref = resolve_ext_ref
 
258
 
 
259
    @property
 
260
    def data(self):
 
261
        if self._data is None:
 
262
            self._data = PackData(self._data_path)
 
263
        return self._data
 
264
 
 
265
    @property
 
266
    def index(self):
 
267
        if self._idx is None:
 
268
            if not os.path.exists(self._idx_path):
 
269
                pb = ui.ui_factory.nested_progress_bar()
 
270
                try:
 
271
                    def report_progress(cur, total):
 
272
                        pb.update("generating index", cur, total)
 
273
                    self.data.create_index(self._idx_path, 
 
274
                        progress=report_progress)
 
275
                finally:
 
276
                    pb.finished()
 
277
            self._idx = load_pack_index(self._idx_path)
 
278
        return self._idx
 
279
 
 
280
    def __del__(self):
 
281
        if self._idx is not None:
 
282
            self._idx.close()
 
283
            os.remove(self._idx_path)
 
284
        if self._data is not None:
 
285
            self._data.close()
 
286
            os.remove(self._data_path)
 
287
 
 
288
 
 
289
class RemoteGitControlDirFormat(GitControlDirFormat):
 
290
    """The .git directory control format."""
 
291
 
 
292
    supports_workingtrees = False
 
293
 
 
294
    @classmethod
 
295
    def _known_formats(self):
 
296
        return set([RemoteGitControlDirFormat()])
 
297
 
 
298
    def open(self, transport, _found=None):
 
299
        """Open this directory.
 
300
 
 
301
        """
 
302
        # we dont grok readonly - git isn't integrated with transport.
 
303
        url = transport.base
 
304
        if url.startswith('readonly+'):
 
305
            url = url[len('readonly+'):]
 
306
        if (not url.startswith("git://") and not url.startswith("git+")):
 
307
            raise NotBranchError(transport.base)
 
308
        if not isinstance(transport, GitSmartTransport):
 
309
            raise NotBranchError(transport.base)
 
310
        lockfiles = GitLockableFiles(transport, GitLock())
 
311
        return RemoteGitDir(transport, lockfiles, self)
 
312
 
 
313
    def get_format_description(self):
 
314
        return "Remote Git Repository"
 
315
 
 
316
    def initialize_on_transport(self, transport):
 
317
        raise UninitializableFormat(self)
 
318
 
 
319
 
102
320
class RemoteGitRepository(GitRepository):
103
321
 
104
322
    def __init__(self, gitdir, lockfiles):
105
323
        GitRepository.__init__(self, gitdir, lockfiles)
106
 
 
107
 
    def fetch_pack(self, determine_wants, graph_walker, pack_data, 
 
324
        self._refs = None
 
325
 
 
326
    @property
 
327
    def user_url(self):
 
328
        return self.control_url
 
329
 
 
330
    def get_parent_map(self, revids):
 
331
        raise GitSmartRemoteNotSupported()
 
332
 
 
333
    def get_refs(self):
 
334
        if self._refs is not None:
 
335
            return self._refs
 
336
        self._refs = self.bzrdir.root_transport.fetch_pack(lambda x: [], None,
 
337
            lambda x: None, lambda x: trace.mutter("git: %s" % x))
 
338
        return self._refs
 
339
 
 
340
    def fetch_pack(self, determine_wants, graph_walker, pack_data,
108
341
                   progress=None):
109
 
        self._transport.fetch_pack(determine_wants, graph_walker, pack_data, 
110
 
            progress)
 
342
        return self._transport.fetch_pack(determine_wants, graph_walker,
 
343
                                          pack_data, progress)
 
344
 
 
345
    def send_pack(self, get_changed_refs, generate_pack_contents):
 
346
        return self._transport.send_pack(get_changed_refs, generate_pack_contents)
 
347
 
 
348
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
 
349
                      progress=None):
 
350
        fd, path = tempfile.mkstemp(suffix=".pack")
 
351
        try:
 
352
            self.fetch_pack(determine_wants, graph_walker,
 
353
                lambda x: os.write(fd, x), progress)
 
354
        finally:
 
355
            os.close(fd)
 
356
        if os.path.getsize(path) == 0:
 
357
            return EmptyObjectStoreIterator()
 
358
        return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
 
359
 
 
360
    def lookup_bzr_revision_id(self, bzr_revid):
 
361
        # This won't work for any round-tripped bzr revisions, but it's a start..
 
362
        try:
 
363
            return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
 
364
        except InvalidRevisionId:
 
365
            raise NoSuchRevision(self, bzr_revid)
 
366
 
 
367
    def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
 
368
        """Lookup a revision id.
 
369
 
 
370
        """
 
371
        if mapping is None:
 
372
            mapping = self.get_mapping()
 
373
        # Not really an easy way to parse foreign revids here..
 
374
        return mapping.revision_id_foreign_to_bzr(foreign_revid)
 
375
 
 
376
 
 
377
class RemoteGitTagDict(GitTags):
 
378
 
 
379
    def get_refs(self):
 
380
        return self.repository.get_refs()
 
381
 
 
382
    def _iter_tag_refs(self, refs):
 
383
        for k, (peeled, unpeeled) in extract_tags(refs).iteritems():
 
384
            yield (k, peeled, unpeeled,
 
385
                  self.branch.mapping.revision_id_foreign_to_bzr(peeled))
 
386
 
 
387
    def set_tag(self, name, revid):
 
388
        # FIXME: Not supported yet, should do a push of a new ref
 
389
        raise NotImplementedError(self.set_tag)
111
390
 
112
391
 
113
392
class RemoteGitBranch(GitBranch):
114
393
 
115
394
    def __init__(self, bzrdir, repository, name, lockfiles):
116
 
        def determine_wants(heads):
117
 
            self._ref = heads[name]
118
 
        bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None, 
119
 
                             lambda x: mutter("git: %s" % x))
120
 
        super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
 
395
        self._sha = None
 
396
        super(RemoteGitBranch, self).__init__(bzrdir, repository, name,
 
397
                lockfiles)
 
398
 
 
399
    def last_revision_info(self):
 
400
        raise GitSmartRemoteNotSupported()
 
401
 
 
402
    @property
 
403
    def user_url(self):
 
404
        return self.control_url
 
405
 
 
406
    @property
 
407
    def control_url(self):
 
408
        return self.base
 
409
 
 
410
    def revision_history(self):
 
411
        raise GitSmartRemoteNotSupported()
121
412
 
122
413
    def last_revision(self):
123
 
        return self.mapping.revision_id_foreign_to_bzr(self._ref)
124
 
 
 
414
        return self.lookup_foreign_revision_id(self.head)
 
415
 
 
416
    def _get_config(self):
 
417
        class EmptyConfig(object):
 
418
 
 
419
            def _get_configobj(self):
 
420
                return config.ConfigObj()
 
421
 
 
422
        return EmptyConfig()
 
423
 
 
424
    @property
 
425
    def head(self):
 
426
        if self._sha is not None:
 
427
            return self._sha
 
428
        heads = self.repository.get_refs()
 
429
        name = branch_name_to_ref(self.name, "HEAD")
 
430
        if name in heads:
 
431
            self._sha = heads[name]
 
432
        else:
 
433
            raise NoSuchRef(self.name)
 
434
        return self._sha
 
435
 
 
436
    def _synchronize_history(self, destination, revision_id):
 
437
        """See Branch._synchronize_history()."""
 
438
        destination.generate_revision_history(self.last_revision())
 
439
 
 
440
    def get_push_location(self):
 
441
        return None
 
442
 
 
443
    def set_push_location(self, url):
 
444
        pass