/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

More work on colocated branch support.

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 self._path
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
        if self._path.startswith("/~/"):
 
195
            return self._path[3:]
 
196
        return self._path
 
197
 
 
198
    def _get_client(self, thin_packs):
 
199
        if self._client is not None:
 
200
            ret = self._client
 
201
            self._client = None
 
202
            return ret
 
203
        location_config = config.LocationConfig(self.base)
 
204
        client = git.client.SSHGitClient(self._host, self._port, self._username,
 
205
            thin_packs=thin_packs, report_activity=self._report_activity)
 
206
        # Set up alternate pack program paths
 
207
        upload_pack = location_config.get_user_option('git_upload_pack')
 
208
        if upload_pack:
 
209
            client.alternative_paths["upload-pack"] = upload_pack
 
210
        receive_pack = location_config.get_user_option('git_receive_pack')
 
211
        if receive_pack:
 
212
            client.alternative_paths["receive-pack"] = receive_pack
 
213
        return client
80
214
 
81
215
 
82
216
class RemoteGitDir(GitDir):
86
220
        self.root_transport = transport
87
221
        self.transport = transport
88
222
        self._lockfiles = lockfiles
 
223
        self._mode_check_done = None
 
224
 
 
225
    @property
 
226
    def user_url(self):
 
227
        return self.control_url
89
228
 
90
229
    def open_repository(self):
91
230
        return RemoteGitRepository(self, self._lockfiles)
92
231
 
93
 
    def open_branch(self):
 
232
    def open_branch(self, name=None, unsupported=False,
 
233
            ignore_fallbacks=False):
94
234
        repo = self.open_repository()
95
 
        # TODO: Support for multiple branches in one bzrdir in bzrlib!
96
 
        return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
 
235
        refname = self._get_selected_ref(name)
 
236
        return RemoteGitBranch(self, repo, refname, self._lockfiles)
97
237
 
98
 
    def open_workingtree(self):
 
238
    def open_workingtree(self, recommend_upgrade=False):
99
239
        raise NotLocalUrl(self.transport.base)
100
240
 
101
241
 
 
242
class EmptyObjectStoreIterator(dict):
 
243
 
 
244
    def iterobjects(self):
 
245
        return []
 
246
 
 
247
 
 
248
class TemporaryPackIterator(Pack):
 
249
 
 
250
    def __init__(self, path, resolve_ext_ref):
 
251
        super(TemporaryPackIterator, self).__init__(path)
 
252
        self.resolve_ext_ref = resolve_ext_ref
 
253
 
 
254
    @property
 
255
    def data(self):
 
256
        if self._data is None:
 
257
            self._data = PackData(self._data_path)
 
258
        return self._data
 
259
 
 
260
    @property
 
261
    def index(self):
 
262
        if self._idx is None:
 
263
            if not os.path.exists(self._idx_path):
 
264
                pb = ui.ui_factory.nested_progress_bar()
 
265
                try:
 
266
                    def report_progress(cur, total):
 
267
                        pb.update("generating index", cur, total)
 
268
                    self.data.create_index(self._idx_path, 
 
269
                        progress=report_progress)
 
270
                finally:
 
271
                    pb.finished()
 
272
            self._idx = load_pack_index(self._idx_path)
 
273
        return self._idx
 
274
 
 
275
    def __del__(self):
 
276
        if self._idx is not None:
 
277
            self._idx.close()
 
278
            os.remove(self._idx_path)
 
279
        if self._data is not None:
 
280
            self._data.close()
 
281
            os.remove(self._data_path)
 
282
 
 
283
 
 
284
class RemoteGitControlDirFormat(GitControlDirFormat):
 
285
    """The .git directory control format."""
 
286
 
 
287
    supports_workingtrees = False
 
288
 
 
289
    @classmethod
 
290
    def _known_formats(self):
 
291
        return set([RemoteGitControlDirFormat()])
 
292
 
 
293
    def open(self, transport, _found=None):
 
294
        """Open this directory.
 
295
 
 
296
        """
 
297
        # we dont grok readonly - git isn't integrated with transport.
 
298
        url = transport.base
 
299
        if url.startswith('readonly+'):
 
300
            url = url[len('readonly+'):]
 
301
        if (not url.startswith("git://") and not url.startswith("git+")):
 
302
            raise NotBranchError(transport.base)
 
303
        if not isinstance(transport, GitSmartTransport):
 
304
            raise NotBranchError(transport.base)
 
305
        lockfiles = GitLockableFiles(transport, GitLock())
 
306
        return RemoteGitDir(transport, lockfiles, self)
 
307
 
 
308
    def get_format_description(self):
 
309
        return "Remote Git Repository"
 
310
 
 
311
    def initialize_on_transport(self, transport):
 
312
        raise UninitializableFormat(self)
 
313
 
 
314
 
102
315
class RemoteGitRepository(GitRepository):
103
316
 
104
317
    def __init__(self, gitdir, lockfiles):
105
318
        GitRepository.__init__(self, gitdir, lockfiles)
106
 
 
107
 
    def fetch_pack(self, determine_wants, graph_walker, pack_data, 
 
319
        self._refs = None
 
320
 
 
321
    @property
 
322
    def user_url(self):
 
323
        return self.control_url
 
324
 
 
325
    def get_parent_map(self, revids):
 
326
        raise GitSmartRemoteNotSupported()
 
327
 
 
328
    def get_refs(self):
 
329
        if self._refs is not None:
 
330
            return self._refs
 
331
        self._refs = self.bzrdir.root_transport.fetch_pack(lambda x: [], None,
 
332
            lambda x: None, lambda x: trace.mutter("git: %s" % x))
 
333
        return self._refs
 
334
 
 
335
    def fetch_pack(self, determine_wants, graph_walker, pack_data,
108
336
                   progress=None):
109
 
        self._transport.fetch_pack(determine_wants, graph_walker, pack_data, 
110
 
            progress)
 
337
        return self._transport.fetch_pack(determine_wants, graph_walker,
 
338
                                          pack_data, progress)
 
339
 
 
340
    def send_pack(self, get_changed_refs, generate_pack_contents):
 
341
        return self._transport.send_pack(get_changed_refs, generate_pack_contents)
 
342
 
 
343
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
 
344
                      progress=None):
 
345
        fd, path = tempfile.mkstemp(suffix=".pack")
 
346
        try:
 
347
            self.fetch_pack(determine_wants, graph_walker,
 
348
                lambda x: os.write(fd, x), progress)
 
349
        finally:
 
350
            os.close(fd)
 
351
        if os.path.getsize(path) == 0:
 
352
            return EmptyObjectStoreIterator()
 
353
        return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
 
354
 
 
355
    def lookup_bzr_revision_id(self, bzr_revid):
 
356
        # This won't work for any round-tripped bzr revisions, but it's a start..
 
357
        try:
 
358
            return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
 
359
        except InvalidRevisionId:
 
360
            raise NoSuchRevision(self, bzr_revid)
 
361
 
 
362
    def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
 
363
        """Lookup a revision id.
 
364
 
 
365
        """
 
366
        if mapping is None:
 
367
            mapping = self.get_mapping()
 
368
        # Not really an easy way to parse foreign revids here..
 
369
        return mapping.revision_id_foreign_to_bzr(foreign_revid)
 
370
 
 
371
 
 
372
class RemoteGitTagDict(GitTags):
 
373
 
 
374
    def get_refs(self):
 
375
        return self.repository.get_refs()
 
376
 
 
377
    def _iter_tag_refs(self, refs):
 
378
        for k, (peeled, unpeeled) in extract_tags(refs).iteritems():
 
379
            yield (k, peeled, unpeeled,
 
380
                  self.branch.mapping.revision_id_foreign_to_bzr(peeled))
 
381
 
 
382
    def set_tag(self, name, revid):
 
383
        # FIXME: Not supported yet, should do a push of a new ref
 
384
        raise NotImplementedError(self.set_tag)
111
385
 
112
386
 
113
387
class RemoteGitBranch(GitBranch):
114
388
 
115
389
    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)
 
390
        self._sha = None
 
391
        super(RemoteGitBranch, self).__init__(bzrdir, repository, name,
 
392
                lockfiles)
 
393
 
 
394
    @property
 
395
    def user_url(self):
 
396
        return self.control_url
 
397
 
 
398
    @property
 
399
    def control_url(self):
 
400
        return self.base
 
401
 
 
402
    def revision_history(self):
 
403
        raise GitSmartRemoteNotSupported()
121
404
 
122
405
    def last_revision(self):
123
 
        return self.mapping.revision_id_foreign_to_bzr(self._ref)
124
 
 
 
406
        return self.lookup_foreign_revision_id(self.head)
 
407
 
 
408
    def _get_config(self):
 
409
        class EmptyConfig(object):
 
410
 
 
411
            def _get_configobj(self):
 
412
                return config.ConfigObj()
 
413
 
 
414
        return EmptyConfig()
 
415
 
 
416
    @property
 
417
    def head(self):
 
418
        if self._sha is not None:
 
419
            return self._sha
 
420
        heads = self.repository.get_refs()
 
421
        name = branch_name_to_ref(self.name, "HEAD")
 
422
        if name in heads:
 
423
            self._sha = heads[name]
 
424
        else:
 
425
            raise NoSuchRef(self.name)
 
426
        return self._sha
 
427
 
 
428
    def _synchronize_history(self, destination, revision_id):
 
429
        """See Branch._synchronize_history()."""
 
430
        destination.generate_revision_history(self.last_revision())
 
431
 
 
432
    def get_push_location(self):
 
433
        return None
 
434
 
 
435
    def set_push_location(self, url):
 
436
        pass