157
163
:param url: Git URL
158
164
:return: Tuple with host, port, username, path.
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("/~"):
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)
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)
210
'GitLab: You are not allowed to push code to protected branches '
212
return PermissionDenied(url, message)
213
212
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
215
214
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
217
216
return RemoteGitError(message)
220
def parse_git_hangup(url, e):
221
"""Parse the error lines from a git servers stderr on hangup.
223
:param url: URL of the remote repository
224
:param e: A HangupException
226
stderr_lines = getattr(e, 'stderr_lines', None)
229
if all(line.startswith(b'remote: ') for line in 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'))
243
219
class GitSmartTransport(Transport):
245
221
def __init__(self, url, _client=None):
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)
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)
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)
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):
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
592
559
revision_id = source.last_revision()
594
if not source.repository.has_revision(revision_id):
595
raise NoSuchRevision(source, revision_id)
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)
606
ref_chain, old_sha = self.get_refs_container().follow(refname)
607
except NotBranchError:
608
actual_refname = refname
612
actual_refname = ref_chain[-1]
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)
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,
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
640
for tagname, revid in source.tags.get_tag_dict().items():
641
if tag_selector and not tag_selector(tagname):
593
for tagname, revid in viewitems(source.tags.get_tag_dict()):
645
new_sha = source_store._lookup_revision_sha1(revid)
647
if source.repository.has_revision(revid):
595
new_sha = source_store._lookup_revision_sha1(revid)
651
598
new_sha = repo.lookup_bzr_revision_id(revid)[0]
652
599
except errors.NoSuchRevision:
655
if not source.repository.has_revision(revid):
657
601
ret[tag_name_to_ref(tagname)] = new_sha
659
603
with source_store.lock_read():
660
def generate_pack_data(have, want, progress=None,
662
git_repo = getattr(source.repository, '_git', None)
664
shallow = git_repo.get_shallow()
668
return source_store.generate_lossy_pack_data(
669
have, want, shallow=shallow,
670
progress=progress, ofs_delta=ofs_delta)
672
return source_store.generate_pack_data(
673
have, want, shallow=shallow,
674
progress=progress, ofs_delta=ofs_delta)
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)
683
raise RemoteGitError(error)
684
for ref, error in dw_result.ref_status.items():
686
trace.warning('unable to open ref %s: %s',
688
else: # dulwich < 0.20.4
605
generate_pack_data = source_store.generate_lossy_pack_data
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)
695
push_result.old_revid = NULL_REVISION
696
if self._refs is not None:
697
update_refs_container(self._refs, new_refs)
612
old_remote = self._refs[refname]
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 = (
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()
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)
739
662
return load_pack_index(path)
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)
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.
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"
779
701
headers["Accept-Encoding"] = "identity"
781
response = self.transport.request(
782
704
('GET' if data is None else 'POST'),
785
headers=headers, retries=8)
787
if response.status == 404:
706
accepted_errors=[200, 404])
707
request.follow_redirections = True
709
response = self.transport._perform(request)
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))
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
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()
811
735
def readlines(self):
812
736
return self._response.readlines()
739
self._response.close()
817
741
return WrapResponse(response), read
820
def _git_url_and_path_from_transport(external_url):
821
url = urlutils.strip_segment_parameters(external_url)
822
return urlparse.urlsplit(url)
825
744
class RemoteGitControlDirFormat(GitControlDirFormat):
826
745
"""The .git directory control format."""
848
763
"""Open this directory.
851
split_url = _git_url_and_path_from_transport(transport.external_url())
766
# we dont grok readonly - git isn't integrated with transport.
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('.')
859
781
raise NotBranchError(transport.base)
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)
864
786
def get_format_description(self):
865
787
return "Remote Git Repository"
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)
1015
raise RemoteGitError(error)
927
self.repository.send_pack(get_changed_refs, generate_pack_data)
1018
930
class RemoteGitBranch(GitBranch):
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)
1104
raise RemoteGitError(error)
1011
self.repository.send_pack(get_changed_refs, generate_pack_data)
1105
1012
self._sha = sha