/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: 2019-02-14 03:30:18 UTC
  • mfrom: (6745.1.3 test-file-ids)
  • Revision ID: breezy.the.bot@gmail.com-20190214033018-4mhv416kiuozgned
Fix a commonly typoed word: compatibility.

Merged from https://code.launchpad.net/~jelmer/brz/compatibility-typos/+merge/363008

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 = {}
 
480
            if refname not in old_refs:
 
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)
92
 
 
93
 
    def open_branch(self):
 
506
        return RemoteGitRepository(self)
 
507
 
 
508
    def get_branch_reference(self, name=None):
 
509
        ref = branch_name_to_ref(name)
 
510
        val = self.get_refs_container().read_ref(ref)
 
511
        if val.startswith(SYMREF):
 
512
            return val[len(SYMREF):]
 
513
        return None
 
514
 
 
515
    def open_branch(self, name=None, unsupported=False,
 
516
                    ignore_fallbacks=False, ref=None, possible_transports=None,
 
517
                    nascent_ok=False):
94
518
        repo = self.open_repository()
95
 
        # TODO: Support for multiple branches in one bzrdir in bzrlib!
96
 
        return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
 
519
        ref = self._get_selected_ref(name, ref)
 
520
        try:
 
521
            if not nascent_ok and ref not in self.get_refs_container():
 
522
                raise NotBranchError(
 
523
                    self.root_transport.base, controldir=self)
 
524
        except NotGitRepository:
 
525
            raise NotBranchError(self.root_transport.base,
 
526
                                 controldir=self)
 
527
        ref_chain, unused_sha = self.get_refs_container().follow(ref)
 
528
        return RemoteGitBranch(self, repo, ref_chain[-1])
97
529
 
98
 
    def open_workingtree(self):
 
530
    def open_workingtree(self, recommend_upgrade=False):
99
531
        raise NotLocalUrl(self.transport.base)
100
532
 
 
533
    def has_workingtree(self):
 
534
        return False
 
535
 
 
536
    def get_peeled(self, name):
 
537
        return self.get_refs_container().get_peeled(name)
 
538
 
 
539
    def get_refs_container(self):
 
540
        if self._refs is not None:
 
541
            return self._refs
 
542
        result = self.fetch_pack(lambda x: None, None,
 
543
                                 lambda x: None,
 
544
                                 lambda x: trace.mutter("git: %s" % x))
 
545
        self._refs = remote_refs_dict_to_container(
 
546
            result.refs, result.symrefs)
 
547
        return self._refs
 
548
 
 
549
    def push_branch(self, source, revision_id=None, overwrite=False,
 
550
                    remember=False, create_prefix=False, lossy=False,
 
551
                    name=None):
 
552
        """Push the source branch into this ControlDir."""
 
553
        if revision_id is None:
 
554
            # No revision supplied by the user, default to the branch
 
555
            # revision
 
556
            revision_id = source.last_revision()
 
557
 
 
558
        push_result = GitPushResult()
 
559
        push_result.workingtree_updated = None
 
560
        push_result.master_branch = None
 
561
        push_result.source_branch = source
 
562
        push_result.stacked_on = None
 
563
        push_result.branch_push_result = None
 
564
        repo = self.find_repository()
 
565
        refname = self._get_selected_ref(name)
 
566
        if isinstance(source, GitBranch) and lossy:
 
567
            raise errors.LossyPushToSameVCS(source.controldir, self)
 
568
        source_store = get_object_store(source.repository)
 
569
        with source_store.lock_read():
 
570
            def get_changed_refs(refs):
 
571
                self._refs = remote_refs_dict_to_container(refs)
 
572
                ret = {}
 
573
                # TODO(jelmer): Unpeel if necessary
 
574
                push_result.new_original_revid = revision_id
 
575
                if lossy:
 
576
                    new_sha = source_store._lookup_revision_sha1(revision_id)
 
577
                else:
 
578
                    try:
 
579
                        new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
 
580
                    except errors.NoSuchRevision:
 
581
                        raise errors.NoRoundtrippingSupport(
 
582
                            source, self.open_branch(name=name, nascent_ok=True))
 
583
                if not overwrite:
 
584
                    if remote_divergence(ret.get(refname), new_sha,
 
585
                                         source_store):
 
586
                        raise DivergedBranches(
 
587
                            source, self.open_branch(name, nascent_ok=True))
 
588
                ret[refname] = new_sha
 
589
                return ret
 
590
            if lossy:
 
591
                generate_pack_data = source_store.generate_lossy_pack_data
 
592
            else:
 
593
                generate_pack_data = source_store.generate_pack_data
 
594
            new_refs = self.send_pack(get_changed_refs, generate_pack_data)
 
595
        push_result.new_revid = repo.lookup_foreign_revision_id(
 
596
            new_refs[refname])
 
597
        try:
 
598
            old_remote = self._refs[refname]
 
599
        except KeyError:
 
600
            old_remote = ZERO_SHA
 
601
        push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
 
602
        self._refs = remote_refs_dict_to_container(new_refs)
 
603
        push_result.target_branch = self.open_branch(name)
 
604
        if old_remote != ZERO_SHA:
 
605
            push_result.branch_push_result = GitBranchPushResult()
 
606
            push_result.branch_push_result.source_branch = source
 
607
            push_result.branch_push_result.target_branch = (
 
608
                push_result.target_branch)
 
609
            push_result.branch_push_result.local_branch = None
 
610
            push_result.branch_push_result.master_branch = (
 
611
                push_result.target_branch)
 
612
            push_result.branch_push_result.old_revid = push_result.old_revid
 
613
            push_result.branch_push_result.new_revid = push_result.new_revid
 
614
            push_result.branch_push_result.new_original_revid = (
 
615
                push_result.new_original_revid)
 
616
        if source.get_push_location() is None or remember:
 
617
            source.set_push_location(push_result.target_branch.base)
 
618
        return push_result
 
619
 
 
620
    def _find_commondir(self):
 
621
        # There is no way to find the commondir, if there is any.
 
622
        return self
 
623
 
 
624
 
 
625
class EmptyObjectStoreIterator(dict):
 
626
 
 
627
    def iterobjects(self):
 
628
        return []
 
629
 
 
630
 
 
631
class TemporaryPackIterator(Pack):
 
632
 
 
633
    def __init__(self, path, resolve_ext_ref):
 
634
        super(TemporaryPackIterator, self).__init__(
 
635
            path, resolve_ext_ref=resolve_ext_ref)
 
636
        self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
 
637
 
 
638
    def _idx_load_or_generate(self, path):
 
639
        if not os.path.exists(path):
 
640
            pb = ui.ui_factory.nested_progress_bar()
 
641
            try:
 
642
                def report_progress(cur, total):
 
643
                    pb.update("generating index", cur, total)
 
644
                self.data.create_index(path,
 
645
                                       progress=report_progress)
 
646
            finally:
 
647
                pb.finished()
 
648
        return load_pack_index(path)
 
649
 
 
650
    def __del__(self):
 
651
        if self._idx is not None:
 
652
            self._idx.close()
 
653
            os.remove(self._idx_path)
 
654
        if self._data is not None:
 
655
            self._data.close()
 
656
            os.remove(self._data_path)
 
657
 
 
658
 
 
659
class BzrGitHttpClient(dulwich.client.HttpGitClient):
 
660
 
 
661
    def __init__(self, transport, *args, **kwargs):
 
662
        self.transport = transport
 
663
        super(BzrGitHttpClient, self).__init__(
 
664
            transport.external_url(), *args, **kwargs)
 
665
 
 
666
    def _http_request(self, url, headers=None, data=None,
 
667
                      allow_compression=False):
 
668
        """Perform HTTP request.
 
669
 
 
670
        :param url: Request URL.
 
671
        :param headers: Optional custom headers to override defaults.
 
672
        :param data: Request data.
 
673
        :param allow_compression: Allow GZipped communication.
 
674
        :return: Tuple (`response`, `read`), where response is an `urllib3`
 
675
            response object with additional `content_type` and
 
676
            `redirect_location` properties, and `read` is a consumable read
 
677
            method for the response data.
 
678
        """
 
679
        from breezy.transport.http._urllib2_wrappers import Request
 
680
        headers['User-agent'] = user_agent_for_github()
 
681
        headers["Pragma"] = "no-cache"
 
682
        if allow_compression:
 
683
            headers["Accept-Encoding"] = "gzip"
 
684
        else:
 
685
            headers["Accept-Encoding"] = "identity"
 
686
 
 
687
        request = Request(
 
688
            ('GET' if data is None else 'POST'),
 
689
            url, data, headers,
 
690
            accepted_errors=[200, 404])
 
691
        request.follow_redirections = True
 
692
 
 
693
        response = self.transport._perform(request)
 
694
 
 
695
        if response.code == 404:
 
696
            raise NotGitRepository()
 
697
        elif response.code != 200:
 
698
            raise GitProtocolError("unexpected http resp %d for %s" %
 
699
                                   (response.code, url))
 
700
 
 
701
        # TODO: Optimization available by adding `preload_content=False` to the
 
702
        # request and just passing the `read` method on instead of going via
 
703
        # `BytesIO`, if we can guarantee that the entire response is consumed
 
704
        # before issuing the next to still allow for connection reuse from the
 
705
        # pool.
 
706
        if response.getheader("Content-Encoding") == "gzip":
 
707
            read = gzip.GzipFile(fileobj=response).read
 
708
        else:
 
709
            read = response.read
 
710
 
 
711
        class WrapResponse(object):
 
712
 
 
713
            def __init__(self, response):
 
714
                self._response = response
 
715
                self.status = response.code
 
716
                self.content_type = response.getheader("Content-Type")
 
717
                self.redirect_location = response.geturl()
 
718
 
 
719
            def readlines(self):
 
720
                return self._response.readlines()
 
721
 
 
722
            def close(self):
 
723
                self._response.close()
 
724
 
 
725
        return WrapResponse(response), read
 
726
 
 
727
 
 
728
class RemoteGitControlDirFormat(GitControlDirFormat):
 
729
    """The .git directory control format."""
 
730
 
 
731
    supports_workingtrees = False
 
732
 
 
733
    @classmethod
 
734
    def _known_formats(self):
 
735
        return set([RemoteGitControlDirFormat()])
 
736
 
 
737
    def get_branch_format(self):
 
738
        return RemoteGitBranchFormat()
 
739
 
 
740
    def is_initializable(self):
 
741
        return False
 
742
 
 
743
    def is_supported(self):
 
744
        return True
 
745
 
 
746
    def open(self, transport, _found=None):
 
747
        """Open this directory.
 
748
 
 
749
        """
 
750
        # we dont grok readonly - git isn't integrated with transport.
 
751
        url = transport.base
 
752
        if url.startswith('readonly+'):
 
753
            url = url[len('readonly+'):]
 
754
        scheme = urlparse.urlsplit(transport.external_url())[0]
 
755
        if isinstance(transport, GitSmartTransport):
 
756
            client = transport._get_client()
 
757
            client_path = transport._get_path()
 
758
        elif scheme in ("http", "https"):
 
759
            client = BzrGitHttpClient(transport)
 
760
            client_path, _ = urlutils.split_segment_parameters(transport._path)
 
761
        elif scheme == 'file':
 
762
            client = dulwich.client.LocalGitClient()
 
763
            client_path = transport.local_abspath('.')
 
764
        else:
 
765
            raise NotBranchError(transport.base)
 
766
        if not _found:
 
767
            pass  # TODO(jelmer): Actually probe for something
 
768
        return RemoteGitDir(transport, self, client, client_path)
 
769
 
 
770
    def get_format_description(self):
 
771
        return "Remote Git Repository"
 
772
 
 
773
    def initialize_on_transport(self, transport):
 
774
        raise UninitializableFormat(self)
 
775
 
 
776
    def supports_transport(self, transport):
 
777
        try:
 
778
            external_url = transport.external_url()
 
779
        except InProcessTransport:
 
780
            raise NotBranchError(path=transport.base)
 
781
        return (external_url.startswith("http:")
 
782
                or external_url.startswith("https:")
 
783
                or external_url.startswith("git+")
 
784
                or external_url.startswith("git:"))
 
785
 
 
786
 
 
787
class GitRemoteRevisionTree(RevisionTree):
 
788
 
 
789
    def archive(self, format, name, root=None, subdir=None, force_mtime=None):
 
790
        """Create an archive of this tree.
 
791
 
 
792
        :param format: Format name (e.g. 'tar')
 
793
        :param name: target file name
 
794
        :param root: Root directory name (or None)
 
795
        :param subdir: Subdirectory to export (or None)
 
796
        :return: Iterator over archive chunks
 
797
        """
 
798
        commit = self._repository.lookup_bzr_revision_id(
 
799
            self.get_revision_id())[0]
 
800
        f = tempfile.SpooledTemporaryFile()
 
801
        # git-upload-archive(1) generaly only supports refs. So let's see if we
 
802
        # can find one.
 
803
        reverse_refs = {
 
804
            v: k for (k, v) in
 
805
            self._repository.controldir.get_refs_container().as_dict().items()}
 
806
        try:
 
807
            committish = reverse_refs[commit]
 
808
        except KeyError:
 
809
            # No? Maybe the user has uploadArchive.allowUnreachable enabled.
 
810
            # Let's hope for the best.
 
811
            committish = commit
 
812
        self._repository.archive(
 
813
            format, committish, f.write,
 
814
            subdirs=([subdir] if subdir else None),
 
815
            prefix=(root + '/') if root else '')
 
816
        f.seek(0)
 
817
        return osutils.file_iterator(f)
 
818
 
 
819
    def is_versioned(self, path):
 
820
        raise GitSmartRemoteNotSupported(self.is_versioned, self)
 
821
 
 
822
    def has_filename(self, path):
 
823
        raise GitSmartRemoteNotSupported(self.has_filename, self)
 
824
 
 
825
    def get_file_text(self, path):
 
826
        raise GitSmartRemoteNotSupported(self.get_file_text, self)
 
827
 
101
828
 
102
829
class RemoteGitRepository(GitRepository):
103
830
 
104
 
    def __init__(self, gitdir, lockfiles):
105
 
        GitRepository.__init__(self, gitdir, lockfiles)
106
 
 
107
 
    def fetch_pack(self, determine_wants, graph_walker, pack_data, 
 
831
    supports_random_access = False
 
832
 
 
833
    @property
 
834
    def user_url(self):
 
835
        return self.control_url
 
836
 
 
837
    def get_parent_map(self, revids):
 
838
        raise GitSmartRemoteNotSupported(self.get_parent_map, self)
 
839
 
 
840
    def archive(self, *args, **kwargs):
 
841
        return self.controldir.archive(*args, **kwargs)
 
842
 
 
843
    def fetch_pack(self, determine_wants, graph_walker, pack_data,
108
844
                   progress=None):
109
 
        self._transport.fetch_pack(determine_wants, graph_walker, pack_data, 
110
 
            progress)
 
845
        return self.controldir.fetch_pack(
 
846
            determine_wants, graph_walker, pack_data, progress)
 
847
 
 
848
    def send_pack(self, get_changed_refs, generate_pack_data):
 
849
        return self.controldir.send_pack(get_changed_refs, generate_pack_data)
 
850
 
 
851
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
 
852
                      progress=None):
 
853
        fd, path = tempfile.mkstemp(suffix=".pack")
 
854
        try:
 
855
            self.fetch_pack(determine_wants, graph_walker,
 
856
                            lambda x: os.write(fd, x), progress)
 
857
        finally:
 
858
            os.close(fd)
 
859
        if os.path.getsize(path) == 0:
 
860
            return EmptyObjectStoreIterator()
 
861
        return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
 
862
 
 
863
    def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
 
864
        # This won't work for any round-tripped bzr revisions, but it's a
 
865
        # start..
 
866
        try:
 
867
            return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
 
868
        except InvalidRevisionId:
 
869
            raise NoSuchRevision(self, bzr_revid)
 
870
 
 
871
    def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
 
872
        """Lookup a revision id.
 
873
 
 
874
        """
 
875
        if mapping is None:
 
876
            mapping = self.get_mapping()
 
877
        # Not really an easy way to parse foreign revids here..
 
878
        return mapping.revision_id_foreign_to_bzr(foreign_revid)
 
879
 
 
880
    def revision_tree(self, revid):
 
881
        return GitRemoteRevisionTree(self, revid)
 
882
 
 
883
    def get_revisions(self, revids):
 
884
        raise GitSmartRemoteNotSupported(self.get_revisions, self)
 
885
 
 
886
    def has_revisions(self, revids):
 
887
        raise GitSmartRemoteNotSupported(self.get_revisions, self)
 
888
 
 
889
 
 
890
class RemoteGitTagDict(GitTags):
 
891
 
 
892
    def set_tag(self, name, revid):
 
893
        sha = self.branch.lookup_bzr_revision_id(revid)[0]
 
894
        self._set_ref(name, sha)
 
895
 
 
896
    def delete_tag(self, name):
 
897
        self._set_ref(name, dulwich.client.ZERO_SHA)
 
898
 
 
899
    def _set_ref(self, name, sha):
 
900
        ref = tag_name_to_ref(name)
 
901
 
 
902
        def get_changed_refs(old_refs):
 
903
            ret = {}
 
904
            if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
 
905
                raise NoSuchTag(name)
 
906
            ret[ref] = sha
 
907
            return ret
 
908
 
 
909
        def generate_pack_data(have, want, ofs_delta=False):
 
910
            return pack_objects_to_data([])
 
911
        self.repository.send_pack(get_changed_refs, generate_pack_data)
111
912
 
112
913
 
113
914
class RemoteGitBranch(GitBranch):
114
915
 
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)
 
916
    def __init__(self, controldir, repository, name):
 
917
        self._sha = None
 
918
        super(RemoteGitBranch, self).__init__(controldir, repository, name,
 
919
                                              RemoteGitBranchFormat())
 
920
 
 
921
    def last_revision_info(self):
 
922
        raise GitSmartRemoteNotSupported(self.last_revision_info, self)
 
923
 
 
924
    @property
 
925
    def user_url(self):
 
926
        return self.control_url
 
927
 
 
928
    @property
 
929
    def control_url(self):
 
930
        return self.base
 
931
 
 
932
    def revision_id_to_revno(self, revision_id):
 
933
        raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
121
934
 
122
935
    def last_revision(self):
123
 
        return self.mapping.revision_id_foreign_to_bzr(self._ref)
124
 
 
 
936
        return self.lookup_foreign_revision_id(self.head)
 
937
 
 
938
    @property
 
939
    def head(self):
 
940
        if self._sha is not None:
 
941
            return self._sha
 
942
        refs = self.controldir.get_refs_container()
 
943
        name = branch_name_to_ref(self.name)
 
944
        try:
 
945
            self._sha = refs[name]
 
946
        except KeyError:
 
947
            raise NoSuchRef(name, self.repository.user_url, refs)
 
948
        return self._sha
 
949
 
 
950
    def _synchronize_history(self, destination, revision_id):
 
951
        """See Branch._synchronize_history()."""
 
952
        if revision_id is None:
 
953
            revision_id = self.last_revision()
 
954
        destination.generate_revision_history(revision_id)
 
955
 
 
956
    def _get_parent_location(self):
 
957
        return None
 
958
 
 
959
    def get_push_location(self):
 
960
        return None
 
961
 
 
962
    def set_push_location(self, url):
 
963
        pass
 
964
 
 
965
    def _iter_tag_refs(self):
 
966
        """Iterate over the tag refs.
 
967
 
 
968
        :param refs: Refs dictionary (name -> git sha1)
 
969
        :return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
 
970
        """
 
971
        refs = self.controldir.get_refs_container()
 
972
        for ref_name, unpeeled in refs.as_dict().items():
 
973
            try:
 
974
                tag_name = ref_to_tag_name(ref_name)
 
975
            except (ValueError, UnicodeDecodeError):
 
976
                continue
 
977
            peeled = refs.get_peeled(ref_name)
 
978
            if peeled is None:
 
979
                # Let's just hope it's a commit
 
980
                peeled = unpeeled
 
981
            if not isinstance(tag_name, text_type):
 
982
                raise TypeError(tag_name)
 
983
            yield (ref_name, tag_name, peeled, unpeeled)
 
984
 
 
985
    def set_last_revision_info(self, revno, revid):
 
986
        self.generate_revision_history(revid)
 
987
 
 
988
    def generate_revision_history(self, revision_id, last_rev=None,
 
989
                                  other_branch=None):
 
990
        sha = self.lookup_bzr_revision_id(revision_id)[0]
 
991
        def get_changed_refs(old_refs):
 
992
            return {self.ref: sha}
 
993
        def generate_pack_data(have, want, ofs_delta=False):
 
994
            return pack_objects_to_data([])
 
995
        self.repository.send_pack(get_changed_refs, generate_pack_data)
 
996
        self._sha = sha
 
997
 
 
998
 
 
999
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
 
1000
    base = {}
 
1001
    peeled = {}
 
1002
    for k, v in refs_dict.items():
 
1003
        if is_peeled(k):
 
1004
            peeled[k[:-3]] = v
 
1005
        else:
 
1006
            base[k] = v
 
1007
    for name, target in symrefs_dict.items():
 
1008
        base[name] = SYMREF + target
 
1009
    ret = DictRefsContainer(base)
 
1010
    ret._peeled = peeled
 
1011
    return ret