157
160
:param url: Git URL
158
161
:return: Tuple with host, port, username, path.
160
parsed_url = urlparse.urlparse(url)
161
path = urlparse.unquote(parsed_url.path)
163
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
164
path = urlparse.unquote(loc)
162
165
if path.startswith("/~"):
164
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
167
(username, hostport) = splituser(netloc)
168
(host, port) = splitnport(hostport, None)
169
return (host, port, username, path)
167
172
class RemoteGitError(BzrError):
186
181
:param message: Message sent by the remote git server
188
183
message = str(message).strip()
189
if (message.startswith("Could not find Repository ")
190
or message == 'Repository not found.'
191
or (message.startswith('Repository ') and
192
message.endswith(' not found.'))):
184
if message.startswith("Could not find Repository "):
193
185
return NotBranchError(url, message)
194
186
if message == "HEAD failed to update":
195
base_url = urlutils.strip_segment_parameters(url)
196
return HeadUpdateFailed(base_url)
197
if message.startswith('access denied or repository not exported:'):
198
extra, path = message.split(':', 1)
199
return PermissionDenied(path.strip(), extra)
200
if message.endswith('You are not allowed to push code to this project.'):
201
return PermissionDenied(url, message)
202
if message.endswith(' does not appear to be a git repository'):
203
return NotBranchError(url, message)
204
if re.match('(.+) is not a valid repository name',
205
message.splitlines()[0]):
206
return NotBranchError(url, message)
208
'GitLab: You are not allowed to push code to protected branches '
210
return PermissionDenied(url, message)
211
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
213
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
187
base_url, _ = urlutils.split_segment_parameters(url)
189
("Unable to update remote HEAD branch. To update the master "
190
"branch, specify the URL %s,branch=master.") % base_url)
214
191
# Don't know, just return it to the user as-is
215
192
return RemoteGitError(message)
218
def parse_git_hangup(url, e):
219
"""Parse the error lines from a git servers stderr on hangup.
221
:param url: URL of the remote repository
222
:param e: A HangupException
224
stderr_lines = getattr(e, 'stderr_lines', None)
227
if all(line.startswith(b'remote: ') for line in stderr_lines):
229
line[len(b'remote: '):] for line in stderr_lines]
230
interesting_lines = [
231
line for line in stderr_lines
232
if line and line.replace(b'=', b'')]
233
if len(interesting_lines) == 1:
234
interesting_line = interesting_lines[0]
235
return parse_git_error(
236
url, interesting_line.decode('utf-8', 'surrogateescape'))
237
return RemoteGitError(
238
b'\n'.join(stderr_lines).decode('utf-8', 'surrogateescape'))
241
195
class GitSmartTransport(Transport):
243
197
def __init__(self, url, _client=None):
416
367
def _gitrepository_class(self):
417
368
return RemoteGitRepository
419
def archive(self, format, committish, write_data, progress=None,
420
write_error=None, subdirs=None, prefix=None):
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)
421
374
if progress is None:
422
375
pb = ui.ui_factory.nested_progress_bar()
423
376
progress = DefaultProgressReporter(pb).progress
426
def progress_wrapper(message):
427
if message.startswith(b"fatal: Unknown archive format \'"):
428
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
429
raise errors.NoSuchExportFormat(format.decode('ascii'))
430
return progress(message)
432
self._client.archive(
433
self._client_path, committish, write_data, progress_wrapper,
435
format=(format.encode('ascii') if format else None),
437
prefix=(encode_git_path(prefix) if prefix else None))
438
except HangupException as e:
439
raise parse_git_hangup(self.transport.external_url(), e)
380
self._client.archive(self._client_path, committish,
381
write_data, progress, write_error, format=format,
382
subdirs=subdirs, prefix=prefix)
440
383
except GitProtocolError as e:
441
384
raise parse_git_error(self.transport.external_url(), e)
443
386
if pb is not None:
446
def fetch_pack(self, determine_wants, graph_walker, pack_data,
389
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
448
390
if progress is None:
449
391
pb = ui.ui_factory.nested_progress_bar()
450
392
progress = DefaultProgressReporter(pb).progress
454
result = self._client.fetch_pack(
455
self._client_path, determine_wants, graph_walker, pack_data,
396
result = self._client.fetch_pack(self._client_path, determine_wants,
397
graph_walker, pack_data, progress)
457
398
if result.refs is None:
459
self._refs = remote_refs_dict_to_container(
460
result.refs, result.symrefs)
400
self._refs = remote_refs_dict_to_container(result.refs, result.symrefs)
462
except HangupException as e:
463
raise parse_git_hangup(self.transport.external_url(), e)
464
402
except GitProtocolError as e:
465
403
raise parse_git_error(self.transport.external_url(), e)
473
411
progress = DefaultProgressReporter(pb).progress
477
def get_changed_refs_wrapper(remote_refs):
478
if self._refs is not None:
479
update_refs_container(self._refs, remote_refs)
480
return get_changed_refs(remote_refs)
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)
482
return self._client.send_pack(
483
self._client_path, get_changed_refs_wrapper,
484
generate_pack_data, progress)
485
except HangupException as e:
486
raise parse_git_hangup(self.transport.external_url(), e)
419
return self._client.send_pack(self._client_path,
420
get_changed_refs_wrapper, generate_pack_data, progress)
487
421
except GitProtocolError as e:
488
422
raise parse_git_error(self.transport.external_url(), e)
495
429
refname = self._get_selected_ref(name, ref)
496
430
if refname != b'HEAD' and refname in self.get_refs_container():
497
431
raise AlreadyBranchError(self.user_url)
498
ref_chain, unused_sha = self.get_refs_container().follow(
499
self._get_selected_ref(name))
500
if ref_chain and ref_chain[0] == b'HEAD':
501
refname = ref_chain[1]
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]
502
436
repo = self.open_repository()
503
437
return RemoteGitBranch(self, repo, refname)
505
439
def destroy_branch(self, name=None):
506
440
refname = self._get_selected_ref(name)
508
441
def get_changed_refs(old_refs):
510
if refname not in old_refs:
443
if not refname in ret:
511
444
raise NotBranchError(self.user_url)
512
445
ret[refname] = dulwich.client.ZERO_SHA
515
447
def generate_pack_data(have, want, ofs_delta=False):
516
448
return pack_objects_to_data([])
517
result = self.send_pack(get_changed_refs, generate_pack_data)
518
if result is not None and not isinstance(result, dict):
519
error = result.ref_status.get(refname)
521
raise RemoteGitError(error)
449
self.send_pack(get_changed_refs, generate_pack_data)
524
452
def user_url(self):
539
467
def open_repository(self):
540
468
return RemoteGitRepository(self)
542
def get_branch_reference(self, name=None):
543
ref = branch_name_to_ref(name)
544
val = self.get_refs_container().read_ref(ref)
545
if val.startswith(SYMREF):
546
return val[len(SYMREF):]
549
470
def open_branch(self, name=None, unsupported=False,
550
ignore_fallbacks=False, ref=None, possible_transports=None,
471
ignore_fallbacks=False, ref=None, possible_transports=None,
552
473
repo = self.open_repository()
553
474
ref = self._get_selected_ref(name, ref)
555
if not nascent_ok and ref not in self.get_refs_container():
556
raise NotBranchError(
557
self.root_transport.base, controldir=self)
558
except NotGitRepository:
475
if not nascent_ok and ref not in self.get_refs_container():
559
476
raise NotBranchError(self.root_transport.base,
561
478
ref_chain, unused_sha = self.get_refs_container().follow(ref)
562
479
return RemoteGitBranch(self, repo, ref_chain[-1])
574
491
if self._refs is not None:
575
492
return self._refs
576
493
result = self.fetch_pack(lambda x: None, None,
578
lambda x: trace.mutter("git: %s" % x))
494
lambda x: None, lambda x: trace.mutter("git: %s" % x))
579
495
self._refs = remote_refs_dict_to_container(
580
result.refs, result.symrefs)
496
result.refs, result.symrefs)
581
497
return self._refs
583
499
def push_branch(self, source, revision_id=None, overwrite=False,
584
500
remember=False, create_prefix=False, lossy=False,
585
name=None, tag_selector=None):
586
502
"""Push the source branch into this ControlDir."""
587
503
if revision_id is None:
588
504
# No revision supplied by the user, default to the branch
590
506
revision_id = source.last_revision()
592
if not source.repository.has_revision(revision_id):
593
raise NoSuchRevision(source, revision_id)
595
508
push_result = GitPushResult()
596
509
push_result.workingtree_updated = None
600
513
push_result.branch_push_result = None
601
514
repo = self.find_repository()
602
515
refname = self._get_selected_ref(name)
603
ref_chain, old_sha = self.get_refs_container().follow(refname)
605
actual_refname = ref_chain[-1]
607
actual_refname = refname
608
516
if isinstance(source, GitBranch) and lossy:
609
517
raise errors.LossyPushToSameVCS(source.controldir, self)
610
518
source_store = get_object_store(source.repository)
611
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
612
def get_changed_refs(remote_refs):
613
if self._refs is not None:
614
update_refs_container(self._refs, remote_refs)
616
# TODO(jelmer): Unpeel if necessary
617
push_result.new_original_revid = revision_id
619
new_sha = source_store._lookup_revision_sha1(revision_id)
622
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
623
except errors.NoSuchRevision:
624
raise errors.NoRoundtrippingSupport(
625
source, self.open_branch(name=name, nascent_ok=True))
627
if remote_divergence(old_sha, new_sha, source_store):
628
raise DivergedBranches(
629
source, self.open_branch(name, nascent_ok=True))
630
ret[actual_refname] = new_sha
632
for tagname, revid in source.tags.get_tag_dict().items():
633
if tag_selector and not tag_selector(tagname):
637
new_sha = source_store._lookup_revision_sha1(revid)
639
if source.repository.has_revision(revid):
643
new_sha = repo.lookup_bzr_revision_id(revid)[0]
644
except errors.NoSuchRevision:
647
if not source.repository.has_revision(revid):
649
ret[tag_name_to_ref(tagname)] = new_sha
651
519
with source_store.lock_read():
652
def generate_pack_data(have, want, progress=None,
654
git_repo = getattr(source.repository, '_git', None)
656
shallow = git_repo.get_shallow()
520
def get_changed_refs(refs):
521
self._refs = remote_refs_dict_to_container(refs)
523
# TODO(jelmer): Unpeel if necessary
524
push_result.new_original_revid = revision_id
660
return source_store.generate_lossy_pack_data(
661
have, want, shallow=shallow,
662
progress=progress, ofs_delta=ofs_delta)
664
return source_store.generate_pack_data(
665
have, want, shallow=shallow,
666
progress=progress, ofs_delta=ofs_delta)
526
new_sha = source_store._lookup_revision_sha1(revision_id)
668
return source_store.generate_pack_data(
669
have, want, progress=progress, ofs_delta=ofs_delta)
670
dw_result = self.send_pack(get_changed_refs, generate_pack_data)
671
if not isinstance(dw_result, dict):
672
new_refs = dw_result.refs
673
error = dw_result.ref_status.get(actual_refname)
675
raise RemoteGitError(error)
676
for ref, error in dw_result.ref_status.items():
678
trace.warning('unable to open ref %s: %s',
680
else: # dulwich < 0.20.4
528
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
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
536
generate_pack_data = source_store.generate_lossy_pack_data
538
generate_pack_data = source_store.generate_pack_data
539
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
682
540
push_result.new_revid = repo.lookup_foreign_revision_id(
683
new_refs[actual_refname])
684
if old_sha is not None:
685
push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
687
push_result.old_revid = NULL_REVISION
688
if self._refs is not None:
689
update_refs_container(self._refs, new_refs)
543
old_remote = self._refs[refname]
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)
690
548
push_result.target_branch = self.open_branch(name)
691
if old_sha is not None:
549
if old_remote != ZERO_SHA:
692
550
push_result.branch_push_result = GitBranchPushResult()
693
551
push_result.branch_push_result.source_branch = source
694
push_result.branch_push_result.target_branch = (
695
push_result.target_branch)
552
push_result.branch_push_result.target_branch = push_result.target_branch
696
553
push_result.branch_push_result.local_branch = None
697
push_result.branch_push_result.master_branch = (
698
push_result.target_branch)
554
push_result.branch_push_result.master_branch = push_result.target_branch
699
555
push_result.branch_push_result.old_revid = push_result.old_revid
700
556
push_result.branch_push_result.new_revid = push_result.new_revid
701
push_result.branch_push_result.new_original_revid = (
702
push_result.new_original_revid)
557
push_result.branch_push_result.new_original_revid = push_result.new_original_revid
703
558
if source.get_push_location() is None or remember:
704
559
source.set_push_location(push_result.target_branch.base)
705
560
return push_result
762
617
`redirect_location` properties, and `read` is a consumable read
763
618
method for the response data.
765
if is_github_url(url):
766
headers['User-agent'] = user_agent_for_github()
620
from breezy.transport.http._urllib2_wrappers import Request
621
headers['User-agent'] = user_agent_for_github()
767
622
headers["Pragma"] = "no-cache"
768
623
if allow_compression:
769
624
headers["Accept-Encoding"] = "gzip"
771
626
headers["Accept-Encoding"] = "identity"
773
response = self.transport.request(
774
629
('GET' if data is None else 'POST'),
777
headers=headers, retries=8)
779
if response.status == 404:
631
accepted_errors=[200, 404])
633
response = self.transport._perform(request)
635
if response.code == 404:
780
636
raise NotGitRepository()
781
elif response.status != 200:
637
elif response.code != 200:
782
638
raise GitProtocolError("unexpected http resp %d for %s" %
783
(response.status, url))
639
(response.code, url))
785
641
# TODO: Optimization available by adding `preload_content=False` to the
786
642
# request and just passing the `read` method on instead of going via
840
684
"""Open this directory.
843
split_url = _git_url_and_path_from_transport(transport.external_url())
687
# we dont grok readonly - git isn't integrated with transport.
689
if url.startswith('readonly+'):
690
url = url[len('readonly+'):]
691
scheme = urlparse.urlsplit(transport.external_url())[0]
844
692
if isinstance(transport, GitSmartTransport):
845
693
client = transport._get_client()
846
elif split_url.scheme in ("http", "https"):
694
client_path = transport._get_path()
695
elif scheme in ("http", "https"):
847
696
client = BzrGitHttpClient(transport)
848
elif split_url.scheme in ('file', ):
697
client_path, _ = urlutils.split_segment_parameters(transport._path)
698
elif scheme == 'file':
849
699
client = dulwich.client.LocalGitClient()
700
client_path = transport.local_abspath('.')
851
702
raise NotBranchError(transport.base)
853
pass # TODO(jelmer): Actually probe for something
854
return RemoteGitDir(transport, self, client, split_url.path)
704
pass # TODO(jelmer): Actually probe for something
705
return RemoteGitDir(transport, self, client, client_path)
856
707
def get_format_description(self):
857
708
return "Remote Git Repository"
897
747
# Let's hope for the best.
898
748
committish = commit
899
749
self._repository.archive(
900
format, committish, f.write,
901
subdirs=([subdir] if subdir else None),
902
prefix=(root + '/') if root else '')
750
format, committish, f.write,
751
subdirs=([subdir] if subdir else None),
752
prefix=(root+'/') if root else '')
904
754
return osutils.file_iterator(f)
906
def is_versioned(self, path):
907
raise GitSmartRemoteNotSupported(self.is_versioned, self)
909
def has_filename(self, path):
910
raise GitSmartRemoteNotSupported(self.has_filename, self)
912
def get_file_text(self, path):
913
raise GitSmartRemoteNotSupported(self.get_file_text, self)
915
def list_files(self, include_root=False, from_dir=None, recursive=True):
916
raise GitSmartRemoteNotSupported(self.list_files, self)
919
757
class RemoteGitRepository(GitRepository):
921
supports_random_access = False
924
760
def user_url(self):
925
761
return self.control_url
933
769
def fetch_pack(self, determine_wants, graph_walker, pack_data,
935
return self.controldir.fetch_pack(
936
determine_wants, graph_walker, pack_data, progress)
771
return self.controldir.fetch_pack(determine_wants, graph_walker,
938
774
def send_pack(self, get_changed_refs, generate_pack_data):
939
775
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
941
777
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
944
779
fd, path = tempfile.mkstemp(suffix=".pack")
946
781
self.fetch_pack(determine_wants, graph_walker,
947
lambda x: os.write(fd, x), progress)
782
lambda x: os.write(fd, x), progress)
950
785
if os.path.getsize(path) == 0:
1073
898
peeled = refs.get_peeled(ref_name)
1074
899
if peeled is None:
1075
# Let's just hope it's a commit
1077
if not isinstance(tag_name, str):
901
peeled = refs.peel_sha(unpeeled).id
903
# Let's just hope it's a commit
905
if type(tag_name) is not unicode:
1078
906
raise TypeError(tag_name)
1079
907
yield (ref_name, tag_name, peeled, unpeeled)
1081
def set_last_revision_info(self, revno, revid):
1082
self.generate_revision_history(revid)
1084
def generate_revision_history(self, revision_id, last_rev=None,
1086
sha = self.lookup_bzr_revision_id(revision_id)[0]
1087
def get_changed_refs(old_refs):
1088
return {self.ref: sha}
1089
def generate_pack_data(have, want, ofs_delta=False):
1090
return pack_objects_to_data([])
1091
result = self.repository.send_pack(
1092
get_changed_refs, generate_pack_data)
1093
if result is not None and not isinstance(result, dict):
1094
error = result.ref_status.get(self.ref)
1096
raise RemoteGitError(error)
1100
910
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):