/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-10 17:18:27 UTC
  • mto: (7143.11.2 unused-imports)
  • mto: This revision was merged to the branch mainline in revision 7144.
  • Revision ID: jelmer@jelmer.uk-20181110171827-46xer5sa9fzgab1q
Add flake8 configuration to monkey patch for lazy imports.

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