/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-02-21 03:58:42 UTC
  • mfrom: (7490.3.4 work)
  • mto: This revision was merged to the branch mainline in revision 7495.
  • Revision ID: jelmer@jelmer.uk-20200221035842-j97r6b74q8cgxb21
merge lp:brz/3.1.

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
import gzip
 
20
from io import BytesIO
 
21
import re
 
22
 
 
23
from .. import (
 
24
    config,
 
25
    debug,
 
26
    errors,
 
27
    osutils,
 
28
    trace,
 
29
    ui,
 
30
    urlutils,
 
31
    )
 
32
from ..push import (
 
33
    PushResult,
 
34
    )
 
35
from ..errors import (
 
36
    AlreadyBranchError,
 
37
    BzrError,
 
38
    DivergedBranches,
 
39
    InProcessTransport,
 
40
    InvalidRevisionId,
 
41
    NoSuchFile,
 
42
    NoSuchRevision,
 
43
    NoSuchTag,
 
44
    NotBranchError,
 
45
    NotLocalUrl,
 
46
    PermissionDenied,
 
47
    UninitializableFormat,
 
48
    )
 
49
from ..revision import NULL_REVISION
 
50
from ..revisiontree import RevisionTree
 
51
from ..transport import (
 
52
    Transport,
 
53
    register_urlparse_netloc_protocol,
 
54
    )
 
55
 
 
56
from . import (
 
57
    lazy_check_versions,
 
58
    is_github_url,
 
59
    user_agent_for_github,
 
60
    )
27
61
lazy_check_versions()
28
62
 
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
 
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
    GitRepositoryFormat,
 
90
    )
 
91
from .refs import (
 
92
    branch_name_to_ref,
 
93
    is_peeled,
 
94
    ref_to_tag_name,
 
95
    tag_name_to_ref,
 
96
    )
34
97
 
 
98
import dulwich
 
99
import dulwich.client
 
100
from dulwich.errors import (
 
101
    GitProtocolError,
 
102
    HangupException,
 
103
    )
 
104
from dulwich.pack import (
 
105
    Pack,
 
106
    pack_objects_to_data,
 
107
    )
 
108
from dulwich.protocol import ZERO_SHA
 
109
from dulwich.refs import (
 
110
    DictRefsContainer,
 
111
    SYMREF,
 
112
    )
 
113
from dulwich.repo import (
 
114
    NotGitRepository,
 
115
    )
35
116
import os
 
117
import select
36
118
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 
 
119
 
 
120
import urllib.parse as urlparse
 
121
from urllib.parse import splituser
 
122
 
 
123
# urlparse only supports a limited number of schemes by default
 
124
register_urlparse_netloc_protocol('git')
 
125
register_urlparse_netloc_protocol('git+ssh')
 
126
 
 
127
from dulwich.pack import load_pack_index
 
128
 
 
129
 
 
130
class GitPushResult(PushResult):
 
131
 
 
132
    def _lookup_revno(self, revid):
 
133
        try:
 
134
            return _quick_lookup_revno(self.source_branch, self.target_branch,
 
135
                                       revid)
 
136
        except GitSmartRemoteNotSupported:
 
137
            return None
 
138
 
 
139
    @property
 
140
    def old_revno(self):
 
141
        return self._lookup_revno(self.old_revid)
 
142
 
 
143
    @property
 
144
    def new_revno(self):
 
145
        return self._lookup_revno(self.new_revid)
 
146
 
 
147
 
 
148
# Don't run any tests on GitSmartTransport as it is not intended to be
44
149
# a full implementation of Transport
45
150
def get_test_permutations():
46
151
    return []
47
152
 
48
153
 
 
154
def split_git_url(url):
 
155
    """Split a Git URL.
 
156
 
 
157
    :param url: Git URL
 
158
    :return: Tuple with host, port, username, path.
 
159
    """
 
160
    parsed_url = urlparse.urlparse(url)
 
161
    path = urlparse.unquote(parsed_url.path)
 
162
    if path.startswith("/~"):
 
163
        path = path[1:]
 
164
    return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
 
165
 
 
166
 
 
167
class RemoteGitError(BzrError):
 
168
 
 
169
    _fmt = "Remote server error: %(msg)s"
 
170
 
 
171
 
 
172
class HeadUpdateFailed(BzrError):
 
173
 
 
174
    _fmt = ("Unable to update remote HEAD branch. To update the master "
 
175
            "branch, specify the URL %(base_url)s,branch=master.")
 
176
 
 
177
    def __init__(self, base_url):
 
178
        super(HeadUpdateFailed, self).__init__()
 
179
        self.base_url = base_url
 
180
 
 
181
 
 
182
def parse_git_error(url, message):
 
183
    """Parse a remote git server error and return a bzr exception.
 
184
 
 
185
    :param url: URL of the remote repository
 
186
    :param message: Message sent by the remote git server
 
187
    """
 
188
    message = str(message).strip()
 
189
    if (message.startswith("Could not find Repository ")
 
190
        or message == 'Repository not found.'
 
191
            or (message.startswith('Repository ') and
 
192
                message.endswith(' not found.'))):
 
193
        return NotBranchError(url, message)
 
194
    if message == "HEAD failed to update":
 
195
        base_url = urlutils.strip_segment_parameters(url)
 
196
        return HeadUpdateFailed(base_url)
 
197
    if message.startswith('access denied or repository not exported:'):
 
198
        extra, path = message.split(':', 1)
 
199
        return PermissionDenied(path.strip(), extra)
 
200
    if message.endswith('You are not allowed to push code to this project.'):
 
201
        return PermissionDenied(url, message)
 
202
    if message.endswith(' does not appear to be a git repository'):
 
203
        return NotBranchError(url, message)
 
204
    if re.match('(.+) is not a valid repository name',
 
205
                message.splitlines()[0]):
 
206
        return NotBranchError(url, message)
 
207
    m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
 
208
    if m:
 
209
        return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
 
210
    # Don't know, just return it to the user as-is
 
211
    return RemoteGitError(message)
 
212
 
 
213
 
49
214
class GitSmartTransport(Transport):
50
215
 
51
216
    def __init__(self, url, _client=None):
52
217
        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)
 
218
        (self._host, self._port, self._username, self._path) = \
 
219
            split_git_url(url)
 
220
        if 'transport' in debug.debug_flags:
 
221
            trace.mutter('host: %r, user: %r, port: %r, path: %r',
 
222
                         self._host, self._username, self._port, self._path)
57
223
        self._client = _client
 
224
        self._stripped_path = self._path.rsplit(",", 1)[0]
 
225
 
 
226
    def external_url(self):
 
227
        return self.base
 
228
 
 
229
    def has(self, relpath):
 
230
        return False
58
231
 
59
232
    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"])
 
233
        raise NotImplementedError(self._get_client)
66
234
 
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)
 
235
    def _get_path(self):
 
236
        return self._stripped_path
73
237
 
74
238
    def get(self, path):
75
239
        raise NoSuchFile(path)
84
248
        else:
85
249
            newurl = urlutils.join(self.base, offset)
86
250
 
87
 
        return GitSmartTransport(newurl, self._client)
 
251
        return self.__class__(newurl, self._client)
 
252
 
 
253
 
 
254
class TCPGitSmartTransport(GitSmartTransport):
 
255
 
 
256
    _scheme = 'git'
 
257
 
 
258
    def _get_client(self):
 
259
        if self._client is not None:
 
260
            ret = self._client
 
261
            self._client = None
 
262
            return ret
 
263
        if self._host == '':
 
264
            # return dulwich.client.LocalGitClient()
 
265
            return dulwich.client.SubprocessGitClient()
 
266
        return dulwich.client.TCPGitClient(
 
267
            self._host, self._port, report_activity=self._report_activity)
 
268
 
 
269
 
 
270
class SSHSocketWrapper(object):
 
271
 
 
272
    def __init__(self, sock):
 
273
        self.sock = sock
 
274
 
 
275
    def read(self, len=None):
 
276
        return self.sock.recv(len)
 
277
 
 
278
    def write(self, data):
 
279
        return self.sock.write(data)
 
280
 
 
281
    def can_read(self):
 
282
        return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
 
283
 
 
284
 
 
285
class DulwichSSHVendor(dulwich.client.SSHVendor):
 
286
 
 
287
    def __init__(self):
 
288
        from ..transport import ssh
 
289
        self.bzr_ssh_vendor = ssh._get_ssh_vendor()
 
290
 
 
291
    def run_command(self, host, command, username=None, port=None):
 
292
        connection = self.bzr_ssh_vendor.connect_ssh(
 
293
            username=username, password=None, port=port, host=host,
 
294
            command=command)
 
295
        (kind, io_object) = connection.get_sock_or_pipes()
 
296
        if kind == 'socket':
 
297
            return SSHSocketWrapper(io_object)
 
298
        else:
 
299
            raise AssertionError("Unknown io object kind %r'" % kind)
 
300
 
 
301
 
 
302
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
 
303
 
 
304
 
 
305
class SSHGitSmartTransport(GitSmartTransport):
 
306
 
 
307
    _scheme = 'git+ssh'
 
308
 
 
309
    def _get_path(self):
 
310
        path = self._stripped_path
 
311
        if path.startswith("/~/"):
 
312
            return path[3:]
 
313
        return path
 
314
 
 
315
    def _get_client(self):
 
316
        if self._client is not None:
 
317
            ret = self._client
 
318
            self._client = None
 
319
            return ret
 
320
        location_config = config.LocationConfig(self.base)
 
321
        client = dulwich.client.SSHGitClient(
 
322
            self._host, self._port, self._username,
 
323
            report_activity=self._report_activity)
 
324
        # Set up alternate pack program paths
 
325
        upload_pack = location_config.get_user_option('git_upload_pack')
 
326
        if upload_pack:
 
327
            client.alternative_paths["upload-pack"] = upload_pack
 
328
        receive_pack = location_config.get_user_option('git_receive_pack')
 
329
        if receive_pack:
 
330
            client.alternative_paths["receive-pack"] = receive_pack
 
331
        return client
 
332
 
 
333
 
 
334
class RemoteGitBranchFormat(GitBranchFormat):
 
335
 
 
336
    def get_format_description(self):
 
337
        return 'Remote Git Branch'
 
338
 
 
339
    @property
 
340
    def _matchingcontroldir(self):
 
341
        return RemoteGitControlDirFormat()
 
342
 
 
343
    def initialize(self, a_controldir, name=None, repository=None,
 
344
                   append_revisions_only=None):
 
345
        raise UninitializableFormat(self)
 
346
 
 
347
 
 
348
class DefaultProgressReporter(object):
 
349
 
 
350
    _GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
 
351
    _GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
 
352
 
 
353
    def __init__(self, pb):
 
354
        self.pb = pb
 
355
 
 
356
    def progress(self, text):
 
357
        text = text.rstrip(b"\r\n")
 
358
        text = text.decode('utf-8')
 
359
        if text.lower().startswith('error: '):
 
360
            trace.show_error('git: %s', text[len(b'error: '):])
 
361
        else:
 
362
            trace.mutter("git: %s", text)
 
363
            g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
 
364
            if g is not None:
 
365
                (text, pct, current, total) = g.groups()
 
366
                self.pb.update(text, int(current), int(total))
 
367
            else:
 
368
                g = self._GIT_PROGRESS_TOTAL_RE.match(text)
 
369
                if g is not None:
 
370
                    (text, total) = g.groups()
 
371
                    self.pb.update(text, None, int(total))
 
372
                else:
 
373
                    trace.note("%s", text)
88
374
 
89
375
 
90
376
class RemoteGitDir(GitDir):
91
377
 
92
 
    def __init__(self, transport, lockfiles, format):
 
378
    def __init__(self, transport, format, client, client_path):
93
379
        self._format = format
94
380
        self.root_transport = transport
95
381
        self.transport = transport
96
 
        self._lockfiles = lockfiles
 
382
        self._mode_check_done = None
 
383
        self._client = client
 
384
        self._client_path = client_path
 
385
        self.base = self.root_transport.base
 
386
        self._refs = None
 
387
 
 
388
    @property
 
389
    def _gitrepository_class(self):
 
390
        return RemoteGitRepository
 
391
 
 
392
    def archive(self, format, committish, write_data, progress=None,
 
393
                write_error=None, subdirs=None, prefix=None):
 
394
        if progress is None:
 
395
            pb = ui.ui_factory.nested_progress_bar()
 
396
            progress = DefaultProgressReporter(pb).progress
 
397
        else:
 
398
            pb = None
 
399
        def progress_wrapper(message):
 
400
            if message.startswith(b"fatal: Unknown archive format \'"):
 
401
                format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
 
402
                raise errors.NoSuchExportFormat(format.decode('ascii'))
 
403
            return progress(message)
 
404
        try:
 
405
            self._client.archive(
 
406
                self._client_path, committish, write_data, progress_wrapper,
 
407
                write_error,
 
408
                format=(format.encode('ascii') if format else None),
 
409
                subdirs=subdirs,
 
410
                prefix=(prefix.encode('utf-8') if prefix else None))
 
411
        except GitProtocolError as e:
 
412
            raise parse_git_error(self.transport.external_url(), e)
 
413
        finally:
 
414
            if pb is not None:
 
415
                pb.finished()
 
416
 
 
417
    def fetch_pack(self, determine_wants, graph_walker, pack_data,
 
418
                   progress=None):
 
419
        if progress is None:
 
420
            pb = ui.ui_factory.nested_progress_bar()
 
421
            progress = DefaultProgressReporter(pb).progress
 
422
        else:
 
423
            pb = None
 
424
        try:
 
425
            result = self._client.fetch_pack(
 
426
                self._client_path, determine_wants, graph_walker, pack_data,
 
427
                progress)
 
428
            if result.refs is None:
 
429
                result.refs = {}
 
430
            self._refs = remote_refs_dict_to_container(
 
431
                result.refs, result.symrefs)
 
432
            return result
 
433
        except GitProtocolError as e:
 
434
            raise parse_git_error(self.transport.external_url(), e)
 
435
        finally:
 
436
            if pb is not None:
 
437
                pb.finished()
 
438
 
 
439
    def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
 
440
        if progress is None:
 
441
            pb = ui.ui_factory.nested_progress_bar()
 
442
            progress = DefaultProgressReporter(pb).progress
 
443
        else:
 
444
            pb = None
 
445
 
 
446
        def get_changed_refs_wrapper(remote_refs):
 
447
            if self._refs is not None:
 
448
                update_refs_container(self._refs, remote_refs)
 
449
            return get_changed_refs(remote_refs)
 
450
        try:
 
451
            return self._client.send_pack(
 
452
                self._client_path, get_changed_refs_wrapper,
 
453
                generate_pack_data, progress)
 
454
        except GitProtocolError as e:
 
455
            raise parse_git_error(self.transport.external_url(), e)
 
456
        finally:
 
457
            if pb is not None:
 
458
                pb.finished()
 
459
 
 
460
    def create_branch(self, name=None, repository=None,
 
461
                      append_revisions_only=None, ref=None):
 
462
        refname = self._get_selected_ref(name, ref)
 
463
        if refname != b'HEAD' and refname in self.get_refs_container():
 
464
            raise AlreadyBranchError(self.user_url)
 
465
        ref_chain, unused_sha = self.get_refs_container().follow(
 
466
            self._get_selected_ref(name))
 
467
        if ref_chain and ref_chain[0] == b'HEAD':
 
468
            refname = ref_chain[1]
 
469
        repo = self.open_repository()
 
470
        return RemoteGitBranch(self, repo, refname)
 
471
 
 
472
    def destroy_branch(self, name=None):
 
473
        refname = self._get_selected_ref(name)
 
474
 
 
475
        def get_changed_refs(old_refs):
 
476
            ret = {}
 
477
            if refname not in old_refs:
 
478
                raise NotBranchError(self.user_url)
 
479
            ret[refname] = dulwich.client.ZERO_SHA
 
480
            return ret
 
481
 
 
482
        def generate_pack_data(have, want, ofs_delta=False):
 
483
            return pack_objects_to_data([])
 
484
        self.send_pack(get_changed_refs, generate_pack_data)
 
485
 
 
486
    @property
 
487
    def user_url(self):
 
488
        return self.control_url
 
489
 
 
490
    @property
 
491
    def user_transport(self):
 
492
        return self.root_transport
 
493
 
 
494
    @property
 
495
    def control_url(self):
 
496
        return self.control_transport.base
 
497
 
 
498
    @property
 
499
    def control_transport(self):
 
500
        return self.root_transport
97
501
 
98
502
    def open_repository(self):
99
 
        return RemoteGitRepository(self, self._lockfiles)
100
 
 
101
 
    def open_branch(self, _unsupported=False):
 
503
        return RemoteGitRepository(self)
 
504
 
 
505
    def get_branch_reference(self, name=None):
 
506
        ref = branch_name_to_ref(name)
 
507
        val = self.get_refs_container().read_ref(ref)
 
508
        if val.startswith(SYMREF):
 
509
            return val[len(SYMREF):]
 
510
        return None
 
511
 
 
512
    def open_branch(self, name=None, unsupported=False,
 
513
                    ignore_fallbacks=False, ref=None, possible_transports=None,
 
514
                    nascent_ok=False):
102
515
        repo = self.open_repository()
103
 
        # TODO: Support for multiple branches in one bzrdir in bzrlib!
104
 
        return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
 
516
        ref = self._get_selected_ref(name, ref)
 
517
        try:
 
518
            if not nascent_ok and ref not in self.get_refs_container():
 
519
                raise NotBranchError(
 
520
                    self.root_transport.base, controldir=self)
 
521
        except NotGitRepository:
 
522
            raise NotBranchError(self.root_transport.base,
 
523
                                 controldir=self)
 
524
        ref_chain, unused_sha = self.get_refs_container().follow(ref)
 
525
        return RemoteGitBranch(self, repo, ref_chain[-1])
105
526
 
106
 
    def open_workingtree(self):
 
527
    def open_workingtree(self, recommend_upgrade=False):
107
528
        raise NotLocalUrl(self.transport.base)
108
529
 
 
530
    def has_workingtree(self):
 
531
        return False
 
532
 
 
533
    def get_peeled(self, name):
 
534
        return self.get_refs_container().get_peeled(name)
 
535
 
 
536
    def get_refs_container(self):
 
537
        if self._refs is not None:
 
538
            return self._refs
 
539
        result = self.fetch_pack(lambda x: None, None,
 
540
                                 lambda x: None,
 
541
                                 lambda x: trace.mutter("git: %s" % x))
 
542
        self._refs = remote_refs_dict_to_container(
 
543
            result.refs, result.symrefs)
 
544
        return self._refs
 
545
 
 
546
    def push_branch(self, source, revision_id=None, overwrite=False,
 
547
                    remember=False, create_prefix=False, lossy=False,
 
548
                    name=None):
 
549
        """Push the source branch into this ControlDir."""
 
550
        if revision_id is None:
 
551
            # No revision supplied by the user, default to the branch
 
552
            # revision
 
553
            revision_id = source.last_revision()
 
554
 
 
555
        push_result = GitPushResult()
 
556
        push_result.workingtree_updated = None
 
557
        push_result.master_branch = None
 
558
        push_result.source_branch = source
 
559
        push_result.stacked_on = None
 
560
        push_result.branch_push_result = None
 
561
        repo = self.find_repository()
 
562
        refname = self._get_selected_ref(name)
 
563
        ref_chain, old_sha = self.get_refs_container().follow(refname)
 
564
        if ref_chain:
 
565
            actual_refname = ref_chain[-1]
 
566
        else:
 
567
            actual_refname = refname
 
568
        if isinstance(source, GitBranch) and lossy:
 
569
            raise errors.LossyPushToSameVCS(source.controldir, self)
 
570
        source_store = get_object_store(source.repository)
 
571
        fetch_tags = source.get_config_stack().get('branch.fetch_tags')
 
572
        def get_changed_refs(remote_refs):
 
573
            if self._refs is not None:
 
574
                update_refs_container(self._refs, remote_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(old_sha, new_sha, source_store):
 
588
                    raise DivergedBranches(
 
589
                        source, self.open_branch(name, nascent_ok=True))
 
590
            ret[actual_refname] = new_sha
 
591
            if fetch_tags:
 
592
                for tagname, revid in source.tags.get_tag_dict().items():
 
593
                    if lossy:
 
594
                        try:
 
595
                            new_sha = source_store._lookup_revision_sha1(revid)
 
596
                        except KeyError:
 
597
                            if source.repository.has_revision(revid):
 
598
                                raise
 
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[actual_refname])
 
614
        if old_sha is not None:
 
615
            push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
 
616
        else:
 
617
            push_result.old_revid = NULL_REVISION
 
618
        if self._refs is not None:
 
619
            update_refs_container(self._refs, new_refs)
 
620
        push_result.target_branch = self.open_branch(name)
 
621
        if old_sha is not None:
 
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
 
109
641
 
110
642
class EmptyObjectStoreIterator(dict):
111
643
 
116
648
class TemporaryPackIterator(Pack):
117
649
 
118
650
    def __init__(self, path, resolve_ext_ref):
119
 
        self.resolve_ext_ref = resolve_ext_ref
120
 
        super(TemporaryPackIterator, self).__init__(path)
 
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)
121
654
 
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
 
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)
128
662
 
129
663
    def __del__(self):
130
 
        os.remove(self._data_path)
131
 
        os.remove(self._idx_path)
 
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=BytesIO(response.read())).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)
132
846
 
133
847
 
134
848
class RemoteGitRepository(GitRepository):
135
849
 
136
 
    def __init__(self, gitdir, lockfiles):
137
 
        GitRepository.__init__(self, gitdir, lockfiles)
138
 
 
139
 
    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,
140
863
                   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):
 
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):
145
872
        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)
 
873
        try:
 
874
            self.fetch_pack(determine_wants, graph_walker,
 
875
                            lambda x: os.write(fd, x), progress)
 
876
        finally:
 
877
            os.close(fd)
148
878
        if os.path.getsize(path) == 0:
149
879
            return EmptyObjectStoreIterator()
150
880
        return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
151
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)
 
931
 
152
932
 
153
933
class RemoteGitBranch(GitBranch):
154
934
 
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)
 
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)
163
953
 
164
954
    def last_revision(self):
165
 
        return self.mapping.revision_id_foreign_to_bzr(self._ref)
 
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
166
968
 
167
969
    def _synchronize_history(self, destination, revision_id):
168
970
        """See Branch._synchronize_history()."""
169
 
        destination.generate_revision_history(self.last_revision())
170
 
 
 
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, str):
 
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
 
1031
 
 
1032
 
 
1033
def update_refs_container(container, refs_dict):
 
1034
    peeled = {}
 
1035
    base = {}
 
1036
    for k, v in refs_dict.items():
 
1037
        if is_peeled(k):
 
1038
            peeled[k[:-3]] = v
 
1039
        else:
 
1040
            base[k] = v
 
1041
    container._peeled = peeled
 
1042
    container._refs.update(base)