/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: 2019-03-05 07:32:38 UTC
  • mto: (7290.1.21 work)
  • mto: This revision was merged to the branch mainline in revision 7311.
  • Revision ID: jelmer@jelmer.uk-20190305073238-zlqn981opwnqsmzi
Add appveyor configuration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Remote dirs, repositories and branches."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
import gzip
20
 
from io import BytesIO
21
22
import re
22
23
 
23
24
from .. import (
46
47
    PermissionDenied,
47
48
    UninitializableFormat,
48
49
    )
49
 
from ..revision import NULL_REVISION
50
50
from ..revisiontree import RevisionTree
 
51
from ..sixish import (
 
52
    text_type,
 
53
    viewitems,
 
54
    )
51
55
from ..transport import (
52
56
    Transport,
53
57
    register_urlparse_netloc_protocol,
55
59
 
56
60
from . import (
57
61
    lazy_check_versions,
58
 
    is_github_url,
59
62
    user_agent_for_github,
60
63
    )
61
64
lazy_check_versions()
76
79
    NoSuchRef,
77
80
    )
78
81
from .mapping import (
79
 
    encode_git_path,
80
82
    mapping_registry,
81
83
    )
82
84
from .object_store import (
87
89
    )
88
90
from .repository import (
89
91
    GitRepository,
90
 
    GitRepositoryFormat,
91
92
    )
92
93
from .refs import (
93
94
    branch_name_to_ref,
116
117
    )
117
118
import os
118
119
import select
 
120
import tempfile
119
121
 
120
 
import urllib.parse as urlparse
121
 
from urllib.parse import splituser
 
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
122
128
 
123
129
# urlparse only supports a limited number of schemes by default
124
130
register_urlparse_netloc_protocol('git')
157
163
    :param url: Git URL
158
164
    :return: Tuple with host, port, username, path.
159
165
    """
160
 
    parsed_url = urlparse.urlparse(url)
161
 
    path = urlparse.unquote(parsed_url.path)
 
166
    (scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
 
167
    path = urlparse.unquote(loc)
162
168
    if path.startswith("/~"):
163
169
        path = path[1:]
164
 
    return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
 
170
    (username, hostport) = splituser(netloc)
 
171
    (host, port) = splitnport(hostport, None)
 
172
    return (host, port, username, path)
165
173
 
166
174
 
167
175
class RemoteGitError(BzrError):
192
200
                message.endswith(' not found.'))):
193
201
        return NotBranchError(url, message)
194
202
    if message == "HEAD failed to update":
195
 
        base_url = urlutils.strip_segment_parameters(url)
 
203
        base_url, _ = urlutils.split_segment_parameters(url)
196
204
        return HeadUpdateFailed(base_url)
197
205
    if message.startswith('access denied or repository not exported:'):
198
 
        extra, path = message.split(':', 1)
199
 
        return PermissionDenied(path.strip(), extra)
 
206
        extra, path = message.split(': ', 1)
 
207
        return PermissionDenied(path, extra)
200
208
    if message.endswith('You are not allowed to push code to this project.'):
201
209
        return PermissionDenied(url, message)
202
210
    if message.endswith(' does not appear to be a git repository'):
203
211
        return NotBranchError(url, message)
204
 
    if message == 'pre-receive hook declined':
205
 
        return PermissionDenied(url, message)
206
 
    if re.match('(.+) is not a valid repository name',
207
 
                message.splitlines()[0]):
208
 
        return NotBranchError(url, message)
209
 
    if message == (
210
 
            'GitLab: You are not allowed to push code to protected branches '
211
 
            'on this project.'):
212
 
        return PermissionDenied(url, message)
213
212
    m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
214
213
    if m:
215
214
        return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
217
216
    return RemoteGitError(message)
218
217
 
219
218
 
220
 
def parse_git_hangup(url, e):
221
 
    """Parse the error lines from a git servers stderr on hangup.
222
 
 
223
 
    :param url: URL of the remote repository
224
 
    :param e: A HangupException
225
 
    """
226
 
    stderr_lines = getattr(e, 'stderr_lines', None)
227
 
    if not stderr_lines:
228
 
        return e
229
 
    if all(line.startswith(b'remote: ') for line in stderr_lines):
230
 
        stderr_lines = [
231
 
            line[len(b'remote: '):] for line in stderr_lines]
232
 
    interesting_lines = [
233
 
        line for line in stderr_lines
234
 
        if line and line.replace(b'=', b'')]
235
 
    if len(interesting_lines) == 1:
236
 
        interesting_line = interesting_lines[0]
237
 
        return parse_git_error(
238
 
            url, interesting_line.decode('utf-8', 'surrogateescape'))
239
 
    return RemoteGitError(
240
 
        b'\n'.join(stderr_lines).decode('utf-8', 'surrogateescape'))
241
 
 
242
 
 
243
219
class GitSmartTransport(Transport):
244
220
 
245
221
    def __init__(self, url, _client=None):
436
412
                write_error,
437
413
                format=(format.encode('ascii') if format else None),
438
414
                subdirs=subdirs,
439
 
                prefix=(encode_git_path(prefix) if prefix else None))
440
 
        except HangupException as e:
441
 
            raise parse_git_hangup(self.transport.external_url(), e)
 
415
                prefix=(prefix.encode('utf-8') if prefix else None))
442
416
        except GitProtocolError as e:
443
417
            raise parse_git_error(self.transport.external_url(), e)
444
418
        finally:
461
435
            self._refs = remote_refs_dict_to_container(
462
436
                result.refs, result.symrefs)
463
437
            return result
464
 
        except HangupException as e:
465
 
            raise parse_git_hangup(self.transport.external_url(), e)
466
438
        except GitProtocolError as e:
467
439
            raise parse_git_error(self.transport.external_url(), e)
468
440
        finally:
476
448
        else:
477
449
            pb = None
478
450
 
479
 
        def get_changed_refs_wrapper(remote_refs):
480
 
            if self._refs is not None:
481
 
                update_refs_container(self._refs, remote_refs)
482
 
            return get_changed_refs(remote_refs)
 
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)
483
455
        try:
484
456
            return self._client.send_pack(
485
457
                self._client_path, get_changed_refs_wrapper,
486
458
                generate_pack_data, progress)
487
 
        except HangupException as e:
488
 
            raise parse_git_hangup(self.transport.external_url(), e)
489
459
        except GitProtocolError as e:
490
460
            raise parse_git_error(self.transport.external_url(), e)
491
461
        finally:
497
467
        refname = self._get_selected_ref(name, ref)
498
468
        if refname != b'HEAD' and refname in self.get_refs_container():
499
469
            raise AlreadyBranchError(self.user_url)
500
 
        ref_chain, unused_sha = self.get_refs_container().follow(
501
 
            self._get_selected_ref(name))
502
 
        if ref_chain and ref_chain[0] == b'HEAD':
503
 
            refname = ref_chain[1]
 
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]
504
475
        repo = self.open_repository()
505
476
        return RemoteGitBranch(self, repo, refname)
506
477
 
516
487
 
517
488
        def generate_pack_data(have, want, ofs_delta=False):
518
489
            return pack_objects_to_data([])
519
 
        result = self.send_pack(get_changed_refs, generate_pack_data)
520
 
        if result is not None and not isinstance(result, dict):
521
 
            error = result.ref_status.get(refname)
522
 
            if error:
523
 
                raise RemoteGitError(error)
 
490
        self.send_pack(get_changed_refs, generate_pack_data)
524
491
 
525
492
    @property
526
493
    def user_url(self):
584
551
 
585
552
    def push_branch(self, source, revision_id=None, overwrite=False,
586
553
                    remember=False, create_prefix=False, lossy=False,
587
 
                    name=None, tag_selector=None):
 
554
                    name=None):
588
555
        """Push the source branch into this ControlDir."""
589
556
        if revision_id is None:
590
557
            # No revision supplied by the user, default to the branch
591
558
            # revision
592
559
            revision_id = source.last_revision()
593
 
        else:
594
 
            if not source.repository.has_revision(revision_id):
595
 
                raise NoSuchRevision(source, revision_id)
596
560
 
597
561
        push_result = GitPushResult()
598
562
        push_result.workingtree_updated = None
602
566
        push_result.branch_push_result = None
603
567
        repo = self.find_repository()
604
568
        refname = self._get_selected_ref(name)
605
 
        try:
606
 
            ref_chain, old_sha = self.get_refs_container().follow(refname)
607
 
        except NotBranchError:
608
 
            actual_refname = refname
609
 
            old_sha = None
610
 
        else:
611
 
            if ref_chain:
612
 
                actual_refname = ref_chain[-1]
613
 
            else:
614
 
                actual_refname = refname
615
569
        if isinstance(source, GitBranch) and lossy:
616
570
            raise errors.LossyPushToSameVCS(source.controldir, self)
617
571
        source_store = get_object_store(source.repository)
618
572
        fetch_tags = source.get_config_stack().get('branch.fetch_tags')
619
 
        def get_changed_refs(remote_refs):
620
 
            if self._refs is not None:
621
 
                update_refs_container(self._refs, remote_refs)
 
573
        def get_changed_refs(refs):
 
574
            self._refs = remote_refs_dict_to_container(refs)
622
575
            ret = {}
623
576
            # TODO(jelmer): Unpeel if necessary
624
577
            push_result.new_original_revid = revision_id
631
584
                    raise errors.NoRoundtrippingSupport(
632
585
                        source, self.open_branch(name=name, nascent_ok=True))
633
586
            if not overwrite:
634
 
                old_sha = remote_refs.get(actual_refname)
635
 
                if remote_divergence(old_sha, new_sha, source_store):
 
587
                if remote_divergence(ret.get(refname), new_sha,
 
588
                                     source_store):
636
589
                    raise DivergedBranches(
637
590
                        source, self.open_branch(name, nascent_ok=True))
638
 
            ret[actual_refname] = new_sha
 
591
            ret[refname] = new_sha
639
592
            if fetch_tags:
640
 
                for tagname, revid in source.tags.get_tag_dict().items():
641
 
                    if tag_selector and not tag_selector(tagname):
642
 
                        continue
 
593
                for tagname, revid in viewitems(source.tags.get_tag_dict()):
643
594
                    if lossy:
644
 
                        try:
645
 
                            new_sha = source_store._lookup_revision_sha1(revid)
646
 
                        except KeyError:
647
 
                            if source.repository.has_revision(revid):
648
 
                                raise
 
595
                        new_sha = source_store._lookup_revision_sha1(revid)
649
596
                    else:
650
597
                        try:
651
598
                            new_sha = repo.lookup_bzr_revision_id(revid)[0]
652
599
                        except errors.NoSuchRevision:
653
600
                            continue
654
 
                        else:
655
 
                            if not source.repository.has_revision(revid):
656
 
                                continue
657
601
                    ret[tag_name_to_ref(tagname)] = new_sha
658
602
            return ret
659
603
        with source_store.lock_read():
660
 
            def generate_pack_data(have, want, progress=None,
661
 
                                   ofs_delta=True):
662
 
                git_repo = getattr(source.repository, '_git', None)
663
 
                if git_repo:
664
 
                    shallow = git_repo.get_shallow()
665
 
                else:
666
 
                    shallow = None
667
 
                if lossy:
668
 
                    return source_store.generate_lossy_pack_data(
669
 
                        have, want, shallow=shallow,
670
 
                        progress=progress, ofs_delta=ofs_delta)
671
 
                elif shallow:
672
 
                    return source_store.generate_pack_data(
673
 
                        have, want, shallow=shallow,
674
 
                        progress=progress, ofs_delta=ofs_delta)
675
 
                else:
676
 
                    return source_store.generate_pack_data(
677
 
                        have, want, progress=progress, ofs_delta=ofs_delta)
678
 
            dw_result = self.send_pack(get_changed_refs, generate_pack_data)
679
 
            if not isinstance(dw_result, dict):
680
 
                new_refs = dw_result.refs
681
 
                error = dw_result.ref_status.get(actual_refname)
682
 
                if error:
683
 
                    raise RemoteGitError(error)
684
 
                for ref, error in dw_result.ref_status.items():
685
 
                    if error:
686
 
                        trace.warning('unable to open ref %s: %s',
687
 
                                      ref, error)
688
 
            else:  # dulwich < 0.20.4
689
 
                new_refs = dw_result
 
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)
690
609
        push_result.new_revid = repo.lookup_foreign_revision_id(
691
 
            new_refs[actual_refname])
692
 
        if old_sha is not None:
693
 
            push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
694
 
        else:
695
 
            push_result.old_revid = NULL_REVISION
696
 
        if self._refs is not None:
697
 
            update_refs_container(self._refs, new_refs)
 
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)
698
617
        push_result.target_branch = self.open_branch(name)
699
 
        if old_sha is not None:
 
618
        if old_remote != ZERO_SHA:
700
619
            push_result.branch_push_result = GitBranchPushResult()
701
620
            push_result.branch_push_result.source_branch = source
702
621
            push_result.branch_push_result.target_branch = (
732
651
 
733
652
    def _idx_load_or_generate(self, path):
734
653
        if not os.path.exists(path):
735
 
            with ui.ui_factory.nested_progress_bar() as pb:
 
654
            pb = ui.ui_factory.nested_progress_bar()
 
655
            try:
736
656
                def report_progress(cur, total):
737
657
                    pb.update("generating index", cur, total)
738
 
                self.data.create_index(path, progress=report_progress)
 
658
                self.data.create_index(path,
 
659
                                       progress=report_progress)
 
660
            finally:
 
661
                pb.finished()
739
662
        return load_pack_index(path)
740
663
 
741
664
    def __del__(self):
754
677
        url = urlutils.URL.from_string(transport.external_url())
755
678
        url.user = url.quoted_user = None
756
679
        url.password = url.quoted_password = None
757
 
        url = urlutils.strip_segment_parameters(str(url))
758
 
        super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
 
680
        super(BzrGitHttpClient, self).__init__(str(url), *args, **kwargs)
759
681
 
760
682
    def _http_request(self, url, headers=None, data=None,
761
683
                      allow_compression=False):
770
692
            `redirect_location` properties, and `read` is a consumable read
771
693
            method for the response data.
772
694
        """
773
 
        if is_github_url(url):
774
 
            headers['User-agent'] = user_agent_for_github()
 
695
        from breezy.transport.http._urllib2_wrappers import Request
 
696
        headers['User-agent'] = user_agent_for_github()
775
697
        headers["Pragma"] = "no-cache"
776
698
        if allow_compression:
777
699
            headers["Accept-Encoding"] = "gzip"
778
700
        else:
779
701
            headers["Accept-Encoding"] = "identity"
780
702
 
781
 
        response = self.transport.request(
 
703
        request = Request(
782
704
            ('GET' if data is None else 'POST'),
783
 
            url,
784
 
            body=data,
785
 
            headers=headers, retries=8)
786
 
 
787
 
        if response.status == 404:
 
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:
788
712
            raise NotGitRepository()
789
 
        elif response.status != 200:
 
713
        elif response.code != 200:
790
714
            raise GitProtocolError("unexpected http resp %d for %s" %
791
 
                                   (response.status, url))
 
715
                                   (response.code, url))
792
716
 
793
717
        # TODO: Optimization available by adding `preload_content=False` to the
794
718
        # request and just passing the `read` method on instead of going via
796
720
        # before issuing the next to still allow for connection reuse from the
797
721
        # pool.
798
722
        if response.getheader("Content-Encoding") == "gzip":
799
 
            read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
 
723
            read = gzip.GzipFile(fileobj=response).read
800
724
        else:
801
725
            read = response.read
802
726
 
804
728
 
805
729
            def __init__(self, response):
806
730
                self._response = response
807
 
                self.status = response.status
 
731
                self.status = response.code
808
732
                self.content_type = response.getheader("Content-Type")
809
 
                self.redirect_location = response._actual.geturl()
 
733
                self.redirect_location = response.geturl()
810
734
 
811
735
            def readlines(self):
812
736
                return self._response.readlines()
813
737
 
814
738
            def close(self):
815
 
                pass
 
739
                self._response.close()
816
740
 
817
741
        return WrapResponse(response), read
818
742
 
819
743
 
820
 
def _git_url_and_path_from_transport(external_url):
821
 
    url = urlutils.strip_segment_parameters(external_url)
822
 
    return urlparse.urlsplit(url)
823
 
 
824
 
 
825
744
class RemoteGitControlDirFormat(GitControlDirFormat):
826
745
    """The .git directory control format."""
827
746
 
834
753
    def get_branch_format(self):
835
754
        return RemoteGitBranchFormat()
836
755
 
837
 
    @property
838
 
    def repository_format(self):
839
 
        return GitRepositoryFormat()
840
 
 
841
756
    def is_initializable(self):
842
757
        return False
843
758
 
848
763
        """Open this directory.
849
764
 
850
765
        """
851
 
        split_url = _git_url_and_path_from_transport(transport.external_url())
 
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]
852
771
        if isinstance(transport, GitSmartTransport):
853
772
            client = transport._get_client()
854
 
        elif split_url.scheme in ("http", "https"):
 
773
            client_path = transport._get_path()
 
774
        elif scheme in ("http", "https"):
855
775
            client = BzrGitHttpClient(transport)
856
 
        elif split_url.scheme in ('file', ):
 
776
            client_path, _ = urlutils.split_segment_parameters(transport._path)
 
777
        elif scheme == 'file':
857
778
            client = dulwich.client.LocalGitClient()
 
779
            client_path = transport.local_abspath('.')
858
780
        else:
859
781
            raise NotBranchError(transport.base)
860
782
        if not _found:
861
783
            pass  # TODO(jelmer): Actually probe for something
862
 
        return RemoteGitDir(transport, self, client, split_url.path)
 
784
        return RemoteGitDir(transport, self, client, client_path)
863
785
 
864
786
    def get_format_description(self):
865
787
        return "Remote Git Repository"
891
813
        """
892
814
        commit = self._repository.lookup_bzr_revision_id(
893
815
            self.get_revision_id())[0]
894
 
        import tempfile
895
816
        f = tempfile.SpooledTemporaryFile()
896
817
        # git-upload-archive(1) generaly only supports refs. So let's see if we
897
818
        # can find one.
920
841
    def get_file_text(self, path):
921
842
        raise GitSmartRemoteNotSupported(self.get_file_text, self)
922
843
 
923
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
924
 
        raise GitSmartRemoteNotSupported(self.list_files, self)
925
 
 
926
844
 
927
845
class RemoteGitRepository(GitRepository):
928
846
 
948
866
 
949
867
    def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
950
868
                      progress=None):
951
 
        import tempfile
952
869
        fd, path = tempfile.mkstemp(suffix=".pack")
953
870
        try:
954
871
            self.fetch_pack(determine_wants, graph_walker,
1007
924
 
1008
925
        def generate_pack_data(have, want, ofs_delta=False):
1009
926
            return pack_objects_to_data([])
1010
 
        result = self.repository.send_pack(
1011
 
            get_changed_refs, generate_pack_data)
1012
 
        if result and not isinstance(result, dict):
1013
 
            error = result.ref_status.get(ref)
1014
 
            if error:
1015
 
                raise RemoteGitError(error)
 
927
        self.repository.send_pack(get_changed_refs, generate_pack_data)
1016
928
 
1017
929
 
1018
930
class RemoteGitBranch(GitBranch):
1082
994
            if peeled is None:
1083
995
                # Let's just hope it's a commit
1084
996
                peeled = unpeeled
1085
 
            if not isinstance(tag_name, str):
 
997
            if not isinstance(tag_name, text_type):
1086
998
                raise TypeError(tag_name)
1087
999
            yield (ref_name, tag_name, peeled, unpeeled)
1088
1000
 
1096
1008
            return {self.ref: sha}
1097
1009
        def generate_pack_data(have, want, ofs_delta=False):
1098
1010
            return pack_objects_to_data([])
1099
 
        result = self.repository.send_pack(
1100
 
            get_changed_refs, generate_pack_data)
1101
 
        if result is not None and not isinstance(result, dict):
1102
 
            error = result.ref_status.get(self.ref)
1103
 
            if error:
1104
 
                raise RemoteGitError(error)
 
1011
        self.repository.send_pack(get_changed_refs, generate_pack_data)
1105
1012
        self._sha = sha
1106
1013
 
1107
1014
 
1118
1025
    ret = DictRefsContainer(base)
1119
1026
    ret._peeled = peeled
1120
1027
    return ret
1121
 
 
1122
 
 
1123
 
def update_refs_container(container, refs_dict):
1124
 
    peeled = {}
1125
 
    base = {}
1126
 
    for k, v in refs_dict.items():
1127
 
        if is_peeled(k):
1128
 
            peeled[k[:-3]] = v
1129
 
        else:
1130
 
            base[k] = v
1131
 
    container._peeled = peeled
1132
 
    container._refs.update(base)