/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: 2018-11-16 11:42:27 UTC
  • mto: (7143.16.20 even-more-cleanups)
  • mto: This revision was merged to the branch mainline in revision 7175.
  • Revision ID: jelmer@jelmer.uk-20181116114227-lwabsodakoymo3ew
Remove flake8 issues now fixed.

Show diffs side-by-side

added added

removed removed

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