/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: 2020-01-12 13:56:10 UTC
  • mto: This revision was merged to the branch mainline in revision 7443.
  • Revision ID: jelmer@jelmer.uk-20200112135610-0a9bct6x4cw7he6y
Add strip_segment_parameters function.

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