/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-06-14 17:59:16 UTC
  • mto: This revision was merged to the branch mainline in revision 7065.
  • Revision ID: jelmer@jelmer.uk-20180614175916-a2e2xh5k533guq1x
Move breezy.plugins.git to breezy.git.

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