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

  • Committer: Jelmer Vernooij
  • Date: 2018-07-26 19:15:27 UTC
  • mto: This revision was merged to the branch mainline in revision 7055.
  • Revision ID: jelmer@jelmer.uk-20180726191527-wniq205k6tzfo1xx
Install fastimport from git.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2008 Canonical Ltd
 
1
# Copyright (C) 2007-2018 Jelmer Vernooij <jelmer@jelmer.uk>
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
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Remote dirs, repositories and branches."""
 
18
 
 
19
from __future__ import absolute_import
 
20
 
 
21
from io import BytesIO
 
22
import re
 
23
 
 
24
from ... import (
 
25
    config,
 
26
    debug,
 
27
    errors,
 
28
    osutils,
 
29
    trace,
 
30
    ui,
 
31
    urlutils,
 
32
    )
 
33
from ...push import (
 
34
    PushResult,
 
35
    )
 
36
from ...errors import (
 
37
    AlreadyBranchError,
 
38
    BzrError,
 
39
    DivergedBranches,
 
40
    InProcessTransport,
 
41
    InvalidRevisionId,
 
42
    NoSuchFile,
 
43
    NoSuchRevision,
 
44
    NoSuchTag,
 
45
    NotBranchError,
 
46
    NotLocalUrl,
 
47
    NoWorkingTree,
 
48
    UninitializableFormat,
 
49
    )
 
50
from ...revisiontree import RevisionTree
 
51
from ...sixish import text_type
 
52
from ...transport import (
 
53
    Transport,
 
54
    register_urlparse_netloc_protocol,
 
55
    )
 
56
 
 
57
from . import (
 
58
    lazy_check_versions,
 
59
    user_agent_for_github,
 
60
    )
 
61
lazy_check_versions()
 
62
 
 
63
from .branch import (
 
64
    GitBranch,
 
65
    GitBranchFormat,
 
66
    GitBranchPushResult,
 
67
    GitTags,
 
68
    _quick_lookup_revno,
 
69
    )
 
70
from .dir import (
 
71
    GitControlDirFormat,
 
72
    GitDir,
 
73
    BareLocalGitControlDirFormat,
 
74
    )
 
75
from .errors import (
 
76
    GitSmartRemoteNotSupported,
 
77
    NoSuchRef,
 
78
    )
 
79
from .mapping import (
 
80
    mapping_registry,
 
81
    )
 
82
from .object_store import (
 
83
    get_object_store,
 
84
    )
 
85
from .push import (
 
86
    remote_divergence,
 
87
    )
 
88
from .repository import (
 
89
    GitRepository,
 
90
    )
 
91
from .refs import (
 
92
    branch_name_to_ref,
 
93
    is_peeled,
 
94
    ref_to_tag_name,
 
95
    tag_name_to_ref,
 
96
    )
 
97
 
 
98
import dulwich
 
99
import dulwich.client
 
100
from dulwich.errors import (
 
101
    GitProtocolError,
 
102
    )
 
103
from dulwich.pack import (
 
104
    Pack,
 
105
    pack_objects_to_data,
 
106
    )
 
107
from dulwich.protocol import ZERO_SHA
 
108
from dulwich.refs import (
 
109
    DictRefsContainer,
 
110
    SYMREF,
 
111
    )
 
112
from dulwich.repo import (
 
113
    NotGitRepository,
 
114
    )
 
115
import os
 
116
import select
 
117
import tempfile
32
118
import urllib
33
 
import urlparse
34
 
 
35
 
from dulwich.pack import PackData
 
119
 
 
120
try:
 
121
    import urllib.parse as urlparse
 
122
    from urllib.parse import splituser, splitnport
 
123
except ImportError:
 
124
    import urlparse
 
125
    from urllib import splituser, splitnport
 
126
 
 
127
# urlparse only supports a limited number of schemes by default
 
128
register_urlparse_netloc_protocol('git')
 
129
register_urlparse_netloc_protocol('git+ssh')
 
130
 
 
131
from dulwich.pack import load_pack_index
 
132
 
 
133
 
 
134
class GitPushResult(PushResult):
 
135
 
 
136
    def _lookup_revno(self, revid):
 
137
        try:
 
138
            return _quick_lookup_revno(self.source_branch, self.target_branch,
 
139
                revid)
 
140
        except GitSmartRemoteNotSupported:
 
141
            return None
 
142
 
 
143
    @property
 
144
    def old_revno(self):
 
145
        return self._lookup_revno(self.old_revid)
 
146
 
 
147
    @property
 
148
    def new_revno(self):
 
149
        return self._lookup_revno(self.new_revid)
 
150
 
 
151
 
 
152
# Don't run any tests on GitSmartTransport as it is not intended to be
 
153
# a full implementation of Transport
 
154
def get_test_permutations():
 
155
    return []
 
156
 
 
157
 
 
158
def split_git_url(url):
 
159
    """Split a Git URL.
 
160
 
 
161
    :param url: Git URL
 
162
    :return: Tuple with host, port, username, path.
 
163
    """
 
164
    (scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
 
165
    path = urlparse.unquote(loc)
 
166
    if path.startswith("/~"):
 
167
        path = path[1:]
 
168
    (username, hostport) = splituser(netloc)
 
169
    (host, port) = splitnport(hostport, None)
 
170
    return (host, port, username, path)
 
171
 
 
172
 
 
173
class RemoteGitError(BzrError):
 
174
 
 
175
    _fmt = "Remote server error: %(msg)s"
 
176
 
 
177
 
 
178
def parse_git_error(url, message):
 
179
    """Parse a remote git server error and return a bzr exception.
 
180
 
 
181
    :param url: URL of the remote repository
 
182
    :param message: Message sent by the remote git server
 
183
    """
 
184
    message = str(message).strip()
 
185
    if message.startswith("Could not find Repository "):
 
186
        return NotBranchError(url, message)
 
187
    if message == "HEAD failed to update":
 
188
        base_url, _ = urlutils.split_segment_parameters(url)
 
189
        raise BzrError(
 
190
            ("Unable to update remote HEAD branch. To update the master "
 
191
             "branch, specify the URL %s,branch=master.") % base_url)
 
192
    # Don't know, just return it to the user as-is
 
193
    return RemoteGitError(message)
36
194
 
37
195
 
38
196
class GitSmartTransport(Transport):
39
197
 
40
198
    def __init__(self, url, _client=None):
41
199
        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)
50
 
 
51
 
    def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
52
 
        if progress is None:
53
 
            def progress(text):
54
 
                info("git: %s" % text)
55
 
        self._client.fetch_pack(self._path, determine_wants, graph_walker, 
56
 
                pack_data, progress)
57
 
 
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)
62
 
        try:
63
 
            p = PackData(path)
64
 
            for o in p.iterobjects():
65
 
                yield o
66
 
        finally:
67
 
            os.remove(path)
 
200
        (self._host, self._port, self._username, self._path) = \
 
201
            split_git_url(url)
 
202
        if 'transport' in debug.debug_flags:
 
203
            trace.mutter('host: %r, user: %r, port: %r, path: %r',
 
204
                         self._host, self._username, self._port, self._path)
 
205
        self._client = _client
 
206
        self._stripped_path = self._path.rsplit(",", 1)[0]
 
207
 
 
208
    def external_url(self):
 
209
        return self.base
 
210
 
 
211
    def has(self, relpath):
 
212
        return False
 
213
 
 
214
    def _get_client(self):
 
215
        raise NotImplementedError(self._get_client)
 
216
 
 
217
    def _get_path(self):
 
218
        return self._stripped_path
68
219
 
69
220
    def get(self, path):
70
221
        raise NoSuchFile(path)
71
222
 
 
223
    def abspath(self, relpath):
 
224
        return urlutils.join(self.base, relpath)
 
225
 
72
226
    def clone(self, offset=None):
73
227
        """See Transport.clone()."""
74
228
        if offset is None:
76
230
        else:
77
231
            newurl = urlutils.join(self.base, offset)
78
232
 
79
 
        return GitSmartTransport(newurl, self._client)
 
233
        return self.__class__(newurl, self._client)
 
234
 
 
235
 
 
236
class TCPGitSmartTransport(GitSmartTransport):
 
237
 
 
238
    _scheme = 'git'
 
239
 
 
240
    def _get_client(self):
 
241
        if self._client is not None:
 
242
            ret = self._client
 
243
            self._client = None
 
244
            return ret
 
245
        if self._host == '':
 
246
            # return dulwich.client.LocalGitClient()
 
247
            return dulwich.client.SubprocessGitClient()
 
248
        return dulwich.client.TCPGitClient(self._host, self._port,
 
249
            report_activity=self._report_activity)
 
250
 
 
251
 
 
252
class SSHSocketWrapper(object):
 
253
 
 
254
    def __init__(self, sock):
 
255
        self.sock = sock
 
256
 
 
257
    def read(self, len=None):
 
258
        return self.sock.recv(len)
 
259
 
 
260
    def write(self, data):
 
261
        return self.sock.write(data)
 
262
 
 
263
    def can_read(self):
 
264
        return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
 
265
 
 
266
 
 
267
class DulwichSSHVendor(dulwich.client.SSHVendor):
 
268
 
 
269
    def __init__(self):
 
270
        from ...transport import ssh
 
271
        self.bzr_ssh_vendor = ssh._get_ssh_vendor()
 
272
 
 
273
    def run_command(self, host, command, username=None, port=None):
 
274
        connection = self.bzr_ssh_vendor.connect_ssh(username=username,
 
275
            password=None, port=port, host=host, command=command)
 
276
        (kind, io_object) = connection.get_sock_or_pipes()
 
277
        if kind == 'socket':
 
278
            return SSHSocketWrapper(io_object)
 
279
        else:
 
280
            raise AssertionError("Unknown io object kind %r'" % kind)
 
281
 
 
282
 
 
283
#dulwich.client.get_ssh_vendor = DulwichSSHVendor
 
284
 
 
285
 
 
286
class SSHGitSmartTransport(GitSmartTransport):
 
287
 
 
288
    _scheme = 'git+ssh'
 
289
 
 
290
    def _get_path(self):
 
291
        path = self._stripped_path
 
292
        if path.startswith("/~/"):
 
293
            return path[3:]
 
294
        return path
 
295
 
 
296
    def _get_client(self):
 
297
        if self._client is not None:
 
298
            ret = self._client
 
299
            self._client = None
 
300
            return ret
 
301
        location_config = config.LocationConfig(self.base)
 
302
        client = dulwich.client.SSHGitClient(self._host, self._port, self._username,
 
303
            report_activity=self._report_activity)
 
304
        # Set up alternate pack program paths
 
305
        upload_pack = location_config.get_user_option('git_upload_pack')
 
306
        if upload_pack:
 
307
            client.alternative_paths["upload-pack"] = upload_pack
 
308
        receive_pack = location_config.get_user_option('git_receive_pack')
 
309
        if receive_pack:
 
310
            client.alternative_paths["receive-pack"] = receive_pack
 
311
        return client
 
312
 
 
313
 
 
314
class RemoteGitBranchFormat(GitBranchFormat):
 
315
 
 
316
    def get_format_description(self):
 
317
        return 'Remote Git Branch'
 
318
 
 
319
    @property
 
320
    def _matchingcontroldir(self):
 
321
        return RemoteGitControlDirFormat()
 
322
 
 
323
    def initialize(self, a_controldir, name=None, repository=None,
 
324
                   append_revisions_only=None):
 
325
        raise UninitializableFormat(self)
 
326
 
 
327
 
 
328
class DefaultProgressReporter(object):
 
329
 
 
330
    _GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
 
331
    _GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
 
332
 
 
333
    def __init__(self, pb):
 
334
        self.pb = pb
 
335
 
 
336
    def progress(self, text):
 
337
        text = text.rstrip(b"\r\n")
 
338
        text = text.decode('utf-8')
 
339
        if text.startswith('error: '):
 
340
            trace.show_error('git: %s', text[len(b'error: '):])
 
341
        else:
 
342
            trace.mutter("git: %s", text)
 
343
            g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
 
344
            if g is not None:
 
345
                (text, pct, current, total) = g.groups()
 
346
                self.pb.update(text, int(current), int(total))
 
347
            else:
 
348
                g = self._GIT_PROGRESS_TOTAL_RE.match(text)
 
349
                if g is not None:
 
350
                    (text, total) = g.groups()
 
351
                    self.pb.update(text, None, int(total))
 
352
                else:
 
353
                    trace.note("%s", text)
80
354
 
81
355
 
82
356
class RemoteGitDir(GitDir):
83
357
 
84
 
    def __init__(self, transport, lockfiles, format):
 
358
    def __init__(self, transport, format, client, client_path):
85
359
        self._format = format
86
360
        self.root_transport = transport
87
361
        self.transport = transport
88
 
        self._lockfiles = lockfiles
 
362
        self._mode_check_done = None
 
363
        self._client = client
 
364
        self._client_path = client_path
 
365
        self.base = self.root_transport.base
 
366
        self._refs = None
 
367
 
 
368
    @property
 
369
    def _gitrepository_class(self):
 
370
        return RemoteGitRepository
 
371
 
 
372
    def archive(self, format, committish, write_data, progress=None, write_error=None,
 
373
                subdirs=None, prefix=None):
 
374
        if format not in ('tar', 'zip'):
 
375
            raise errors.NoSuchExportFormat(format)
 
376
        if progress is None:
 
377
            pb = ui.ui_factory.nested_progress_bar()
 
378
            progress = DefaultProgressReporter(pb).progress
 
379
        else:
 
380
            pb = None
 
381
        try:
 
382
            self._client.archive(self._client_path, committish,
 
383
                write_data, progress, write_error, format=format,
 
384
                subdirs=subdirs, prefix=prefix)
 
385
        except GitProtocolError as e:
 
386
            raise parse_git_error(self.transport.external_url(), e)
 
387
        finally:
 
388
            if pb is not None:
 
389
                pb.finished()
 
390
 
 
391
    def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
 
392
        if progress is None:
 
393
            pb = ui.ui_factory.nested_progress_bar()
 
394
            progress = DefaultProgressReporter(pb).progress
 
395
        else:
 
396
            pb = None
 
397
        try:
 
398
            result = self._client.fetch_pack(self._client_path, determine_wants,
 
399
                graph_walker, pack_data, progress)
 
400
            if result.refs is None:
 
401
                result.refs = {}
 
402
            self._refs = remote_refs_dict_to_container(result.refs, result.symrefs)
 
403
            return result
 
404
        except GitProtocolError as e:
 
405
            raise parse_git_error(self.transport.external_url(), e)
 
406
        finally:
 
407
            if pb is not None:
 
408
                pb.finished()
 
409
 
 
410
    def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
 
411
        if progress is None:
 
412
            pb = ui.ui_factory.nested_progress_bar()
 
413
            progress = DefaultProgressReporter(pb).progress
 
414
        else:
 
415
            pb = None
 
416
        def get_changed_refs_wrapper(refs):
 
417
            # TODO(jelmer): This drops symref information
 
418
            self._refs = remote_refs_dict_to_container(refs)
 
419
            return get_changed_refs(refs)
 
420
        try:
 
421
            return self._client.send_pack(self._client_path,
 
422
                    get_changed_refs_wrapper, generate_pack_data, progress)
 
423
        except GitProtocolError as e:
 
424
            raise parse_git_error(self.transport.external_url(), e)
 
425
        finally:
 
426
            if pb is not None:
 
427
                pb.finished()
 
428
 
 
429
    def create_branch(self, name=None, repository=None,
 
430
                      append_revisions_only=None, ref=None):
 
431
        refname = self._get_selected_ref(name, ref)
 
432
        if refname != b'HEAD' and refname in self.get_refs_container():
 
433
            raise AlreadyBranchError(self.user_url)
 
434
        if refname in self.get_refs_container():
 
435
            ref_chain, unused_sha = self.get_refs_container().follow(self._get_selected_ref(None))
 
436
            if ref_chain[0] == b'HEAD':
 
437
                refname = ref_chain[1]
 
438
        repo = self.open_repository()
 
439
        return RemoteGitBranch(self, repo, refname)
 
440
 
 
441
    def destroy_branch(self, name=None):
 
442
        refname = self._get_selected_ref(name)
 
443
        def get_changed_refs(old_refs):
 
444
            ret = dict(old_refs)
 
445
            if not refname in ret:
 
446
                raise NotBranchError(self.user_url)
 
447
            ret[refname] = dulwich.client.ZERO_SHA
 
448
            return ret
 
449
        def generate_pack_data(have, want, ofs_delta=False):
 
450
            return pack_objects_to_data([])
 
451
        self.send_pack(get_changed_refs, generate_pack_data)
 
452
 
 
453
    @property
 
454
    def user_url(self):
 
455
        return self.control_url
 
456
 
 
457
    @property
 
458
    def user_transport(self):
 
459
        return self.root_transport
 
460
 
 
461
    @property
 
462
    def control_url(self):
 
463
        return self.control_transport.base
 
464
 
 
465
    @property
 
466
    def control_transport(self):
 
467
        return self.root_transport
89
468
 
90
469
    def open_repository(self):
91
 
        return RemoteGitRepository(self, self._lockfiles)
 
470
        return RemoteGitRepository(self)
92
471
 
93
 
    def open_branch(self):
 
472
    def open_branch(self, name=None, unsupported=False,
 
473
            ignore_fallbacks=False, ref=None, possible_transports=None,
 
474
            nascent_ok=False):
94
475
        repo = self.open_repository()
95
 
        # TODO: Support for multiple branches in one bzrdir in bzrlib!
96
 
        return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
 
476
        ref = self._get_selected_ref(name, ref)
 
477
        if not nascent_ok and ref not in self.get_refs_container():
 
478
            raise NotBranchError(self.root_transport.base,
 
479
                    controldir=self)
 
480
        ref_chain, unused_sha = self.get_refs_container().follow(ref)
 
481
        return RemoteGitBranch(self, repo, ref_chain[-1])
97
482
 
98
 
    def open_workingtree(self):
 
483
    def open_workingtree(self, recommend_upgrade=False):
99
484
        raise NotLocalUrl(self.transport.base)
100
485
 
 
486
    def has_workingtree(self):
 
487
        return False
 
488
 
 
489
    def get_peeled(self, name):
 
490
        return self.get_refs_container().get_peeled(name)
 
491
 
 
492
    def get_refs_container(self):
 
493
        if self._refs is not None:
 
494
            return self._refs
 
495
        result = self.fetch_pack(lambda x: None, None,
 
496
            lambda x: None, lambda x: trace.mutter("git: %s" % x))
 
497
        self._refs = remote_refs_dict_to_container(
 
498
                result.refs, result.symrefs)
 
499
        return self._refs
 
500
 
 
501
    def push_branch(self, source, revision_id=None, overwrite=False,
 
502
                    remember=False, create_prefix=False, lossy=False,
 
503
                    name=None):
 
504
        """Push the source branch into this ControlDir."""
 
505
        if revision_id is None:
 
506
            # No revision supplied by the user, default to the branch
 
507
            # revision
 
508
            revision_id = source.last_revision()
 
509
 
 
510
        push_result = GitPushResult()
 
511
        push_result.workingtree_updated = None
 
512
        push_result.master_branch = None
 
513
        push_result.source_branch = source
 
514
        push_result.stacked_on = None
 
515
        push_result.branch_push_result = None
 
516
        repo = self.find_repository()
 
517
        refname = self._get_selected_ref(name)
 
518
        if isinstance(source, GitBranch) and lossy:
 
519
            raise errors.LossyPushToSameVCS(source.controldir, self)
 
520
        source_store = get_object_store(source.repository)
 
521
        with source_store.lock_read():
 
522
            def get_changed_refs(refs):
 
523
                self._refs = remote_refs_dict_to_container(refs)
 
524
                ret = dict(refs)
 
525
                # TODO(jelmer): Unpeel if necessary
 
526
                push_result.new_original_revid = revision_id
 
527
                if lossy:
 
528
                    new_sha = source_store._lookup_revision_sha1(revision_id)
 
529
                else:
 
530
                    new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
 
531
                if not overwrite:
 
532
                    if remote_divergence(ret.get(refname), new_sha, source_store):
 
533
                        raise DivergedBranches(
 
534
                                source, self.open_branch(name, nascent_ok=True))
 
535
                ret[refname] = new_sha
 
536
                return ret
 
537
            if lossy:
 
538
                generate_pack_data = source_store.generate_lossy_pack_data
 
539
            else:
 
540
                generate_pack_data = source_store.generate_pack_data
 
541
            new_refs = self.send_pack(get_changed_refs, generate_pack_data)
 
542
        push_result.new_revid = repo.lookup_foreign_revision_id(
 
543
                new_refs[refname])
 
544
        try:
 
545
            old_remote = self._refs[refname]
 
546
        except KeyError:
 
547
            old_remote = ZERO_SHA
 
548
        push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
 
549
        self._refs = remote_refs_dict_to_container(new_refs)
 
550
        push_result.target_branch = self.open_branch(name)
 
551
        if old_remote != ZERO_SHA:
 
552
            push_result.branch_push_result = GitBranchPushResult()
 
553
            push_result.branch_push_result.source_branch = source
 
554
            push_result.branch_push_result.target_branch = push_result.target_branch
 
555
            push_result.branch_push_result.local_branch = None
 
556
            push_result.branch_push_result.master_branch = push_result.target_branch
 
557
            push_result.branch_push_result.old_revid = push_result.old_revid
 
558
            push_result.branch_push_result.new_revid = push_result.new_revid
 
559
            push_result.branch_push_result.new_original_revid = push_result.new_original_revid
 
560
        if source.get_push_location() is None or remember:
 
561
            source.set_push_location(push_result.target_branch.base)
 
562
        return push_result
 
563
 
 
564
    def _find_commondir(self):
 
565
        # There is no way to find the commondir, if there is any.
 
566
        return self
 
567
 
 
568
 
 
569
class EmptyObjectStoreIterator(dict):
 
570
 
 
571
    def iterobjects(self):
 
572
        return []
 
573
 
 
574
 
 
575
class TemporaryPackIterator(Pack):
 
576
 
 
577
    def __init__(self, path, resolve_ext_ref):
 
578
        super(TemporaryPackIterator, self).__init__(
 
579
            path, resolve_ext_ref=resolve_ext_ref)
 
580
        self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
 
581
 
 
582
    def _idx_load_or_generate(self, path):
 
583
        if not os.path.exists(path):
 
584
            pb = ui.ui_factory.nested_progress_bar()
 
585
            try:
 
586
                def report_progress(cur, total):
 
587
                    pb.update("generating index", cur, total)
 
588
                self.data.create_index(path,
 
589
                    progress=report_progress)
 
590
            finally:
 
591
                pb.finished()
 
592
        return load_pack_index(path)
 
593
 
 
594
    def __del__(self):
 
595
        if self._idx is not None:
 
596
            self._idx.close()
 
597
            os.remove(self._idx_path)
 
598
        if self._data is not None:
 
599
            self._data.close()
 
600
            os.remove(self._data_path)
 
601
 
 
602
 
 
603
class BzrGitHttpClient(dulwich.client.HttpGitClient):
 
604
 
 
605
    def __init__(self, transport, *args, **kwargs):
 
606
        self.transport = transport
 
607
        super(BzrGitHttpClient, self).__init__(transport.external_url(), *args, **kwargs)
 
608
 
 
609
    def _http_request(self, url, headers=None, data=None,
 
610
                      allow_compression=False):
 
611
        """Perform HTTP request.
 
612
 
 
613
        :param url: Request URL.
 
614
        :param headers: Optional custom headers to override defaults.
 
615
        :param data: Request data.
 
616
        :param allow_compression: Allow GZipped communication.
 
617
        :return: Tuple (`response`, `read`), where response is an `urllib3`
 
618
            response object with additional `content_type` and
 
619
            `redirect_location` properties, and `read` is a consumable read
 
620
            method for the response data.
 
621
        """
 
622
        from breezy.transport.http._urllib2_wrappers import Request
 
623
        headers['User-agent'] = user_agent_for_github()
 
624
        headers["Pragma"] = "no-cache"
 
625
        if allow_compression:
 
626
            headers["Accept-Encoding"] = "gzip"
 
627
        else:
 
628
            headers["Accept-Encoding"] = "identity"
 
629
 
 
630
        request = Request(
 
631
            ('GET' if data is None else 'POST'),
 
632
            url, data, headers,
 
633
            accepted_errors=[200, 404])
 
634
 
 
635
        response = self.transport._perform(request)
 
636
 
 
637
        if response.code == 404:
 
638
            raise NotGitRepository()
 
639
        elif response.code != 200:
 
640
            raise GitProtocolError("unexpected http resp %d for %s" %
 
641
                                   (response.code, url))
 
642
 
 
643
        # TODO: Optimization available by adding `preload_content=False` to the
 
644
        # request and just passing the `read` method on instead of going via
 
645
        # `BytesIO`, if we can guarantee that the entire response is consumed
 
646
        # before issuing the next to still allow for connection reuse from the
 
647
        # pool.
 
648
        if response.getheader("Content-Encoding") == "gzip":
 
649
            read = gzip.GzipFile(fileobj=response).read
 
650
        else:
 
651
            read = response.read
 
652
 
 
653
        class WrapResponse(object):
 
654
 
 
655
            def __init__(self, response):
 
656
                self._response = response
 
657
                self.status = response.code
 
658
                self.content_type = response.getheader("Content-Type")
 
659
                self.redirect_location = response.geturl()
 
660
 
 
661
            def close(self):
 
662
                self._response.close()
 
663
 
 
664
        return WrapResponse(response), read
 
665
 
 
666
 
 
667
class RemoteGitControlDirFormat(GitControlDirFormat):
 
668
    """The .git directory control format."""
 
669
 
 
670
    supports_workingtrees = False
 
671
 
 
672
    @classmethod
 
673
    def _known_formats(self):
 
674
        return set([RemoteGitControlDirFormat()])
 
675
 
 
676
    def get_branch_format(self):
 
677
        return RemoteGitBranchFormat()
 
678
 
 
679
    def is_initializable(self):
 
680
        return False
 
681
 
 
682
    def is_supported(self):
 
683
        return True
 
684
 
 
685
    def open(self, transport, _found=None):
 
686
        """Open this directory.
 
687
 
 
688
        """
 
689
        # we dont grok readonly - git isn't integrated with transport.
 
690
        url = transport.base
 
691
        if url.startswith('readonly+'):
 
692
            url = url[len('readonly+'):]
 
693
        scheme = urlparse.urlsplit(transport.external_url())[0]
 
694
        if isinstance(transport, GitSmartTransport):
 
695
            client = transport._get_client()
 
696
            client_path = transport._get_path()
 
697
        elif scheme in ("http", "https"):
 
698
            client = BzrGitHttpClient(transport)
 
699
            client_path, _ = urlutils.split_segment_parameters(transport._path)
 
700
        elif scheme == 'file':
 
701
            client = dulwich.client.LocalGitClient()
 
702
            client_path = transport.local_abspath('.')
 
703
        else:
 
704
            raise NotBranchError(transport.base)
 
705
        if not _found:
 
706
            pass # TODO(jelmer): Actually probe for something
 
707
        return RemoteGitDir(transport, self, client, client_path)
 
708
 
 
709
    def get_format_description(self):
 
710
        return "Remote Git Repository"
 
711
 
 
712
    def initialize_on_transport(self, transport):
 
713
        raise UninitializableFormat(self)
 
714
 
 
715
    def supports_transport(self, transport):
 
716
        try:
 
717
            external_url = transport.external_url()
 
718
        except InProcessTransport:
 
719
            raise NotBranchError(path=transport.base)
 
720
        return (external_url.startswith("http:") or
 
721
                external_url.startswith("https:") or
 
722
                external_url.startswith("git+") or
 
723
                external_url.startswith("git:"))
 
724
 
 
725
 
 
726
class GitRemoteRevisionTree(RevisionTree):
 
727
 
 
728
    def archive(self, format, name, root=None, subdir=None, force_mtime=None):
 
729
        """Create an archive of this tree.
 
730
 
 
731
        :param format: Format name (e.g. 'tar')
 
732
        :param name: target file name
 
733
        :param root: Root directory name (or None)
 
734
        :param subdir: Subdirectory to export (or None)
 
735
        :return: Iterator over archive chunks
 
736
        """
 
737
        commit = self._repository.lookup_bzr_revision_id(
 
738
            self.get_revision_id())[0]
 
739
        f = tempfile.SpooledTemporaryFile()
 
740
        # git-upload-archive(1) generaly only supports refs. So let's see if we
 
741
        # can find one.
 
742
        reverse_refs = {
 
743
                v: k for (k, v) in
 
744
                self._repository.controldir.get_refs_container().as_dict().items()}
 
745
        try:
 
746
            committish = reverse_refs[commit]
 
747
        except KeyError:
 
748
            # No? Maybe the user has uploadArchive.allowUnreachable enabled.
 
749
            # Let's hope for the best.
 
750
            committish = commit
 
751
        self._repository.archive(
 
752
                format, committish, f.write,
 
753
                subdirs=([subdir] if subdir else None),
 
754
                prefix=(root+'/') if root else '')
 
755
        f.seek(0)
 
756
        return osutils.file_iterator(f)
 
757
 
101
758
 
102
759
class RemoteGitRepository(GitRepository):
103
760
 
104
 
    def __init__(self, gitdir, lockfiles):
105
 
        GitRepository.__init__(self, gitdir, lockfiles)
106
 
 
107
 
    def fetch_pack(self, determine_wants, graph_walker, pack_data, 
 
761
    @property
 
762
    def user_url(self):
 
763
        return self.control_url
 
764
 
 
765
    def get_parent_map(self, revids):
 
766
        raise GitSmartRemoteNotSupported(self.get_parent_map, self)
 
767
 
 
768
    def archive(self, *args, **kwargs):
 
769
        return self.controldir.archive(*args, **kwargs)
 
770
 
 
771
    def fetch_pack(self, determine_wants, graph_walker, pack_data,
108
772
                   progress=None):
109
 
        self._transport.fetch_pack(determine_wants, graph_walker, pack_data, 
110
 
            progress)
 
773
        return self.controldir.fetch_pack(determine_wants, graph_walker,
 
774
                                          pack_data, progress)
 
775
 
 
776
    def send_pack(self, get_changed_refs, generate_pack_data):
 
777
        return self.controldir.send_pack(get_changed_refs, generate_pack_data)
 
778
 
 
779
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
 
780
                      progress=None):
 
781
        fd, path = tempfile.mkstemp(suffix=".pack")
 
782
        try:
 
783
            self.fetch_pack(determine_wants, graph_walker,
 
784
                lambda x: os.write(fd, x), progress)
 
785
        finally:
 
786
            os.close(fd)
 
787
        if os.path.getsize(path) == 0:
 
788
            return EmptyObjectStoreIterator()
 
789
        return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
 
790
 
 
791
    def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
 
792
        # This won't work for any round-tripped bzr revisions, but it's a start..
 
793
        try:
 
794
            return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
 
795
        except InvalidRevisionId:
 
796
            raise NoSuchRevision(self, bzr_revid)
 
797
 
 
798
    def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
 
799
        """Lookup a revision id.
 
800
 
 
801
        """
 
802
        if mapping is None:
 
803
            mapping = self.get_mapping()
 
804
        # Not really an easy way to parse foreign revids here..
 
805
        return mapping.revision_id_foreign_to_bzr(foreign_revid)
 
806
 
 
807
    def revision_tree(self, revid):
 
808
        return GitRemoteRevisionTree(self, revid)
 
809
 
 
810
    def get_revisions(self, revids):
 
811
        raise GitSmartRemoteNotSupported(self.get_revisions, self)
 
812
 
 
813
    def has_revisions(self, revids):
 
814
        raise GitSmartRemoteNotSupported(self.get_revisions, self)
 
815
 
 
816
 
 
817
class RemoteGitTagDict(GitTags):
 
818
 
 
819
    def set_tag(self, name, revid):
 
820
        sha = self.branch.lookup_bzr_revision_id(revid)[0]
 
821
        self._set_ref(name, sha)
 
822
 
 
823
    def delete_tag(self, name):
 
824
        self._set_ref(name, dulwich.client.ZERO_SHA)
 
825
 
 
826
    def _set_ref(self, name, sha):
 
827
        ref = tag_name_to_ref(name)
 
828
        def get_changed_refs(old_refs):
 
829
            ret = dict(old_refs)
 
830
            if sha == dulwich.client.ZERO_SHA and ref not in ret:
 
831
                raise NoSuchTag(name)
 
832
            ret[ref] = sha
 
833
            return ret
 
834
        def generate_pack_data(have, want, ofs_delta=False):
 
835
            return pack_objects_to_data([])
 
836
        self.repository.send_pack(get_changed_refs, generate_pack_data)
111
837
 
112
838
 
113
839
class RemoteGitBranch(GitBranch):
114
840
 
115
 
    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)
 
841
    def __init__(self, controldir, repository, name):
 
842
        self._sha = None
 
843
        super(RemoteGitBranch, self).__init__(controldir, repository, name,
 
844
                RemoteGitBranchFormat())
 
845
 
 
846
    def last_revision_info(self):
 
847
        raise GitSmartRemoteNotSupported(self.last_revision_info, self)
 
848
 
 
849
    @property
 
850
    def user_url(self):
 
851
        return self.control_url
 
852
 
 
853
    @property
 
854
    def control_url(self):
 
855
        return self.base
 
856
 
 
857
    def revision_id_to_revno(self, revision_id):
 
858
        raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
121
859
 
122
860
    def last_revision(self):
123
 
        return self.mapping.revision_id_foreign_to_bzr(self._ref)
124
 
 
 
861
        return self.lookup_foreign_revision_id(self.head)
 
862
 
 
863
    @property
 
864
    def head(self):
 
865
        if self._sha is not None:
 
866
            return self._sha
 
867
        refs = self.controldir.get_refs_container()
 
868
        name = branch_name_to_ref(self.name)
 
869
        try:
 
870
            self._sha = refs[name]
 
871
        except KeyError:
 
872
            raise NoSuchRef(name, self.repository.user_url, refs)
 
873
        return self._sha
 
874
 
 
875
    def _synchronize_history(self, destination, revision_id):
 
876
        """See Branch._synchronize_history()."""
 
877
        destination.generate_revision_history(self.last_revision())
 
878
 
 
879
    def _get_parent_location(self):
 
880
        return None
 
881
 
 
882
    def get_push_location(self):
 
883
        return None
 
884
 
 
885
    def set_push_location(self, url):
 
886
        pass
 
887
 
 
888
    def _iter_tag_refs(self):
 
889
        """Iterate over the tag refs.
 
890
 
 
891
        :param refs: Refs dictionary (name -> git sha1)
 
892
        :return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
 
893
        """
 
894
        refs = self.controldir.get_refs_container()
 
895
        for ref_name, unpeeled in refs.as_dict().items():
 
896
            try:
 
897
                tag_name = ref_to_tag_name(ref_name)
 
898
            except (ValueError, UnicodeDecodeError):
 
899
                continue
 
900
            peeled = refs.get_peeled(ref_name)
 
901
            if peeled is None:
 
902
                try:
 
903
                    peeled = refs.peel_sha(unpeeled).id
 
904
                except KeyError:
 
905
                    # Let's just hope it's a commit
 
906
                    peeled = unpeeled
 
907
            if not isinstance(tag_name, text_type):
 
908
                raise TypeError(tag_name)
 
909
            yield (ref_name, tag_name, peeled, unpeeled)
 
910
 
 
911
 
 
912
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
 
913
    base = {}
 
914
    peeled = {}
 
915
    for k, v in refs_dict.items():
 
916
        if is_peeled(k):
 
917
            peeled[k[:-3]] = v
 
918
        else:
 
919
            base[k] = v
 
920
            peeled[k] = v
 
921
    for name, target in symrefs_dict.items():
 
922
        base[name] = SYMREF + target
 
923
    ret = DictRefsContainer(base)
 
924
    ret._peeled = peeled
 
925
    return ret