/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

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