/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-05-05 23:32:39 UTC
  • mto: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200505233239-kdmnmscn8eisltk6
Add a breezy.__main__ module.

Show diffs side-by-side

added added

removed removed

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