/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2018-11-16 18:26:22 UTC
  • mfrom: (7167.1.4 run-flake8)
  • Revision ID: breezy.the.bot@gmail.com-20181116182622-qw3gan3hz78a2imw
Add a flake8 test.

Merged from https://code.launchpad.net/~jelmer/brz/run-flake8/+merge/358902

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