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

  • Committer: Jelmer Vernooij
  • Date: 2019-02-17 03:45:31 UTC
  • mto: (7290.1.6 work)
  • mto: This revision was merged to the branch mainline in revision 7295.
  • Revision ID: jelmer@jelmer.uk-20190217034531-vw7oc2bo5hdd41jn
Drop documentation about removed pkgimport.conf.

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