/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-01-01 21:08:01 UTC
  • mto: This revision was merged to the branch mainline in revision 7231.
  • Revision ID: jelmer@jelmer.uk-20190101210801-2dlsv7b1lvydmpkl
Fix tests.

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