167
161
:param url: Git URL
168
162
:return: Tuple with host, port, username, path.
170
parsed_url = urlparse.urlparse(url)
171
path = urlparse.unquote(parsed_url.path)
164
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
165
path = urlparse.unquote(loc)
172
166
if path.startswith("/~"):
174
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
168
(username, hostport) = splituser(netloc)
169
(host, port) = splitnport(hostport, None)
170
return (host, port, username, path)
177
173
class RemoteGitError(BzrError):
196
182
:param message: Message sent by the remote git server
198
184
message = str(message).strip()
199
if (message.startswith("Could not find Repository ")
200
or message == 'Repository not found.'
201
or (message.startswith('Repository ') and
202
message.endswith(' not found.'))):
185
if message.startswith("Could not find Repository "):
203
186
return NotBranchError(url, message)
204
187
if message == "HEAD failed to update":
205
base_url = urlutils.strip_segment_parameters(url)
206
return HeadUpdateFailed(base_url)
207
if message.startswith('access denied or repository not exported:'):
208
extra, path = message.split(':', 1)
209
return PermissionDenied(path.strip(), extra)
210
if message.endswith('You are not allowed to push code to this project.'):
211
return PermissionDenied(url, message)
212
if message.endswith(' does not appear to be a git repository'):
213
return NotBranchError(url, message)
214
if re.match('(.+) is not a valid repository name',
215
message.splitlines()[0]):
216
return NotBranchError(url, message)
217
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
219
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
188
base_url, _ = urlutils.split_segment_parameters(url)
190
("Unable to update remote HEAD branch. To update the master "
191
"branch, specify the URL %s,branch=master.") % base_url)
220
192
# Don't know, just return it to the user as-is
221
193
return RemoteGitError(message)
295
267
class DulwichSSHVendor(dulwich.client.SSHVendor):
297
269
def __init__(self):
298
from ..transport import ssh
270
from ...transport import ssh
299
271
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
301
273
def run_command(self, host, command, username=None, port=None):
302
connection = self.bzr_ssh_vendor.connect_ssh(
303
username=username, password=None, port=port, host=host,
274
connection = self.bzr_ssh_vendor.connect_ssh(username=username,
275
password=None, port=port, host=host, command=command)
305
276
(kind, io_object) = connection.get_sock_or_pipes()
306
277
if kind == 'socket':
307
278
return SSHSocketWrapper(io_object)
399
368
def _gitrepository_class(self):
400
369
return RemoteGitRepository
402
def archive(self, format, committish, write_data, progress=None,
403
write_error=None, subdirs=None, prefix=None):
371
def archive(self, format, committish, write_data, progress=None, write_error=None,
372
subdirs=None, prefix=None):
373
if format not in ('tar', 'zip'):
374
raise errors.NoSuchExportFormat(format)
404
375
if progress is None:
405
376
pb = ui.ui_factory.nested_progress_bar()
406
377
progress = DefaultProgressReporter(pb).progress
409
def progress_wrapper(message):
410
if message.startswith(b"fatal: Unknown archive format \'"):
411
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
412
raise errors.NoSuchExportFormat(format.decode('ascii'))
413
return progress(message)
415
self._client.archive(
416
self._client_path, committish, write_data, progress_wrapper,
418
format=(format.encode('ascii') if format else None),
420
prefix=(prefix.encode('utf-8') if prefix else None))
381
self._client.archive(self._client_path, committish,
382
write_data, progress, write_error, format=format,
383
subdirs=subdirs, prefix=prefix)
421
384
except GitProtocolError as e:
422
385
raise parse_git_error(self.transport.external_url(), e)
424
387
if pb is not None:
427
def fetch_pack(self, determine_wants, graph_walker, pack_data,
390
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
429
391
if progress is None:
430
392
pb = ui.ui_factory.nested_progress_bar()
431
393
progress = DefaultProgressReporter(pb).progress
435
result = self._client.fetch_pack(
436
self._client_path, determine_wants, graph_walker, pack_data,
397
result = self._client.fetch_pack(self._client_path, determine_wants,
398
graph_walker, pack_data, progress)
438
399
if result.refs is None:
440
self._refs = remote_refs_dict_to_container(
441
result.refs, result.symrefs)
401
self._refs = remote_refs_dict_to_container(result.refs, result.symrefs)
443
403
except GitProtocolError as e:
444
404
raise parse_git_error(self.transport.external_url(), e)
452
412
progress = DefaultProgressReporter(pb).progress
456
def get_changed_refs_wrapper(remote_refs):
457
if self._refs is not None:
458
update_refs_container(self._refs, remote_refs)
459
return get_changed_refs(remote_refs)
415
def get_changed_refs_wrapper(refs):
416
# TODO(jelmer): This drops symref information
417
self._refs = remote_refs_dict_to_container(refs)
418
return get_changed_refs(refs)
461
return self._client.send_pack(
462
self._client_path, get_changed_refs_wrapper,
463
generate_pack_data, progress)
420
return self._client.send_pack(self._client_path,
421
get_changed_refs_wrapper, generate_pack_data, progress)
464
422
except GitProtocolError as e:
465
423
raise parse_git_error(self.transport.external_url(), e)
472
430
refname = self._get_selected_ref(name, ref)
473
431
if refname != b'HEAD' and refname in self.get_refs_container():
474
432
raise AlreadyBranchError(self.user_url)
475
ref_chain, unused_sha = self.get_refs_container().follow(
476
self._get_selected_ref(name))
477
if ref_chain and ref_chain[0] == b'HEAD':
478
refname = ref_chain[1]
433
if refname in self.get_refs_container():
434
ref_chain, unused_sha = self.get_refs_container().follow(self._get_selected_ref(None))
435
if ref_chain[0] == b'HEAD':
436
refname = ref_chain[1]
479
437
repo = self.open_repository()
480
438
return RemoteGitBranch(self, repo, refname)
482
440
def destroy_branch(self, name=None):
483
441
refname = self._get_selected_ref(name)
485
442
def get_changed_refs(old_refs):
487
if refname not in old_refs:
444
if not refname in ret:
488
445
raise NotBranchError(self.user_url)
489
446
ret[refname] = dulwich.client.ZERO_SHA
492
448
def generate_pack_data(have, want, ofs_delta=False):
493
449
return pack_objects_to_data([])
494
450
self.send_pack(get_changed_refs, generate_pack_data)
512
468
def open_repository(self):
513
469
return RemoteGitRepository(self)
515
def get_branch_reference(self, name=None):
516
ref = branch_name_to_ref(name)
517
val = self.get_refs_container().read_ref(ref)
518
if val.startswith(SYMREF):
519
return val[len(SYMREF):]
522
471
def open_branch(self, name=None, unsupported=False,
523
ignore_fallbacks=False, ref=None, possible_transports=None,
472
ignore_fallbacks=False, ref=None, possible_transports=None,
525
474
repo = self.open_repository()
526
475
ref = self._get_selected_ref(name, ref)
528
if not nascent_ok and ref not in self.get_refs_container():
529
raise NotBranchError(
530
self.root_transport.base, controldir=self)
531
except NotGitRepository:
476
if not nascent_ok and ref not in self.get_refs_container():
532
477
raise NotBranchError(self.root_transport.base,
534
479
ref_chain, unused_sha = self.get_refs_container().follow(ref)
535
480
return RemoteGitBranch(self, repo, ref_chain[-1])
547
492
if self._refs is not None:
548
493
return self._refs
549
494
result = self.fetch_pack(lambda x: None, None,
551
lambda x: trace.mutter("git: %s" % x))
495
lambda x: None, lambda x: trace.mutter("git: %s" % x))
552
496
self._refs = remote_refs_dict_to_container(
553
result.refs, result.symrefs)
497
result.refs, result.symrefs)
554
498
return self._refs
556
500
def push_branch(self, source, revision_id=None, overwrite=False,
557
501
remember=False, create_prefix=False, lossy=False,
558
name=None, tag_selector=None):
559
503
"""Push the source branch into this ControlDir."""
560
504
if revision_id is None:
561
505
# No revision supplied by the user, default to the branch
570
514
push_result.branch_push_result = None
571
515
repo = self.find_repository()
572
516
refname = self._get_selected_ref(name)
573
ref_chain, old_sha = self.get_refs_container().follow(refname)
575
actual_refname = ref_chain[-1]
577
actual_refname = refname
578
517
if isinstance(source, GitBranch) and lossy:
579
518
raise errors.LossyPushToSameVCS(source.controldir, self)
580
519
source_store = get_object_store(source.repository)
581
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
582
def get_changed_refs(remote_refs):
583
if self._refs is not None:
584
update_refs_container(self._refs, remote_refs)
586
# TODO(jelmer): Unpeel if necessary
587
push_result.new_original_revid = revision_id
589
new_sha = source_store._lookup_revision_sha1(revision_id)
520
with source_store.lock_read():
521
def get_changed_refs(refs):
522
self._refs = remote_refs_dict_to_container(refs)
524
# TODO(jelmer): Unpeel if necessary
525
push_result.new_original_revid = revision_id
527
new_sha = source_store._lookup_revision_sha1(revision_id)
592
529
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
593
except errors.NoSuchRevision:
594
raise errors.NoRoundtrippingSupport(
595
source, self.open_branch(name=name, nascent_ok=True))
597
if remote_divergence(old_sha, new_sha, source_store):
598
raise DivergedBranches(
599
source, self.open_branch(name, nascent_ok=True))
600
ret[actual_refname] = new_sha
602
for tagname, revid in viewitems(source.tags.get_tag_dict()):
603
if tag_selector and not tag_selector(tagname):
607
new_sha = source_store._lookup_revision_sha1(revid)
609
if source.repository.has_revision(revid):
613
new_sha = repo.lookup_bzr_revision_id(revid)[0]
614
except errors.NoSuchRevision:
616
ret[tag_name_to_ref(tagname)] = new_sha
618
with source_store.lock_read():
531
if remote_divergence(ret.get(refname), new_sha, source_store):
532
raise DivergedBranches(
533
source, self.open_branch(name, nascent_ok=True))
534
ret[refname] = new_sha
620
537
generate_pack_data = source_store.generate_lossy_pack_data
622
539
generate_pack_data = source_store.generate_pack_data
623
540
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
624
541
push_result.new_revid = repo.lookup_foreign_revision_id(
625
new_refs[actual_refname])
626
if old_sha is not None:
627
push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
629
push_result.old_revid = NULL_REVISION
630
if self._refs is not None:
631
update_refs_container(self._refs, new_refs)
544
old_remote = self._refs[refname]
546
old_remote = ZERO_SHA
547
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
548
self._refs = remote_refs_dict_to_container(new_refs)
632
549
push_result.target_branch = self.open_branch(name)
633
if old_sha is not None:
550
if old_remote != ZERO_SHA:
634
551
push_result.branch_push_result = GitBranchPushResult()
635
552
push_result.branch_push_result.source_branch = source
636
push_result.branch_push_result.target_branch = (
637
push_result.target_branch)
553
push_result.branch_push_result.target_branch = push_result.target_branch
638
554
push_result.branch_push_result.local_branch = None
639
push_result.branch_push_result.master_branch = (
640
push_result.target_branch)
555
push_result.branch_push_result.master_branch = push_result.target_branch
641
556
push_result.branch_push_result.old_revid = push_result.old_revid
642
557
push_result.branch_push_result.new_revid = push_result.new_revid
643
push_result.branch_push_result.new_original_revid = (
644
push_result.new_original_revid)
558
push_result.branch_push_result.new_original_revid = push_result.new_original_revid
645
559
if source.get_push_location() is None or remember:
646
560
source.set_push_location(push_result.target_branch.base)
647
561
return push_result
667
581
def _idx_load_or_generate(self, path):
668
582
if not os.path.exists(path):
669
with ui.ui_factory.nested_progress_bar() as pb:
583
pb = ui.ui_factory.nested_progress_bar()
670
585
def report_progress(cur, total):
671
586
pb.update("generating index", cur, total)
672
self.data.create_index(path, progress=report_progress)
587
self.data.create_index(path,
588
progress=report_progress)
673
591
return load_pack_index(path)
675
593
def __del__(self):
686
604
def __init__(self, transport, *args, **kwargs):
687
605
self.transport = transport
688
url = urlutils.URL.from_string(transport.external_url())
689
url.user = url.quoted_user = None
690
url.password = url.quoted_password = None
691
url = urlutils.strip_segment_parameters(str(url))
692
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
606
super(BzrGitHttpClient, self).__init__(transport.external_url(), *args, **kwargs)
694
608
def _http_request(self, url, headers=None, data=None,
695
609
allow_compression=False):
704
618
`redirect_location` properties, and `read` is a consumable read
705
619
method for the response data.
707
if is_github_url(url):
708
headers['User-agent'] = user_agent_for_github()
621
from breezy.transport.http._urllib2_wrappers import Request
622
headers['User-agent'] = user_agent_for_github()
709
623
headers["Pragma"] = "no-cache"
710
624
if allow_compression:
711
625
headers["Accept-Encoding"] = "gzip"
713
627
headers["Accept-Encoding"] = "identity"
715
response = self.transport.request(
716
630
('GET' if data is None else 'POST'),
719
headers=headers, retries=8)
721
if response.status == 404:
632
accepted_errors=[200, 404])
634
response = self.transport._perform(request)
636
if response.code == 404:
722
637
raise NotGitRepository()
723
elif response.status != 200:
638
elif response.code != 200:
724
639
raise GitProtocolError("unexpected http resp %d for %s" %
725
(response.status, url))
640
(response.code, url))
727
642
# TODO: Optimization available by adding `preload_content=False` to the
728
643
# request and just passing the `read` method on instead of going via
739
654
def __init__(self, response):
740
655
self._response = response
741
self.status = response.status
656
self.status = response.code
742
657
self.content_type = response.getheader("Content-Type")
743
self.redirect_location = response._actual.geturl()
746
return self._response.readlines()
658
self.redirect_location = response.geturl()
661
self._response.close()
751
663
return WrapResponse(response), read
754
def _git_url_and_path_from_transport(external_url):
755
url = urlutils.strip_segment_parameters(external_url)
756
return urlparse.urlsplit(url)
759
666
class RemoteGitControlDirFormat(GitControlDirFormat):
760
667
"""The .git directory control format."""
782
685
"""Open this directory.
785
split_url = _git_url_and_path_from_transport(transport.external_url())
688
# we dont grok readonly - git isn't integrated with transport.
690
if url.startswith('readonly+'):
691
url = url[len('readonly+'):]
692
scheme = urlparse.urlsplit(transport.external_url())[0]
786
693
if isinstance(transport, GitSmartTransport):
787
694
client = transport._get_client()
788
elif split_url.scheme in ("http", "https"):
695
client_path = transport._get_path()
696
elif scheme in ("http", "https"):
789
697
client = BzrGitHttpClient(transport)
790
elif split_url.scheme in ('file', ):
698
client_path, _ = urlutils.split_segment_parameters(transport._path)
699
elif scheme == 'file':
791
700
client = dulwich.client.LocalGitClient()
701
client_path = transport.local_abspath('.')
793
703
raise NotBranchError(transport.base)
795
pass # TODO(jelmer): Actually probe for something
796
return RemoteGitDir(transport, self, client, split_url.path)
705
pass # TODO(jelmer): Actually probe for something
706
return RemoteGitDir(transport, self, client, client_path)
798
708
def get_format_description(self):
799
709
return "Remote Git Repository"
838
748
# Let's hope for the best.
839
749
committish = commit
840
750
self._repository.archive(
841
format, committish, f.write,
842
subdirs=([subdir] if subdir else None),
843
prefix=(root + '/') if root else '')
751
format, committish, f.write,
752
subdirs=([subdir] if subdir else None),
753
prefix=(root+'/') if root else '')
845
755
return osutils.file_iterator(f)
847
def is_versioned(self, path):
848
raise GitSmartRemoteNotSupported(self.is_versioned, self)
850
def has_filename(self, path):
851
raise GitSmartRemoteNotSupported(self.has_filename, self)
853
def get_file_text(self, path):
854
raise GitSmartRemoteNotSupported(self.get_file_text, self)
856
def list_files(self, include_root=False, from_dir=None, recursive=True):
857
raise GitSmartRemoteNotSupported(self.list_files, self)
860
758
class RemoteGitRepository(GitRepository):
862
supports_random_access = False
865
761
def user_url(self):
866
762
return self.control_url
1008
899
peeled = refs.get_peeled(ref_name)
1009
900
if peeled is None:
1010
# Let's just hope it's a commit
902
peeled = refs.peel_sha(unpeeled).id
904
# Let's just hope it's a commit
1012
906
if not isinstance(tag_name, text_type):
1013
907
raise TypeError(tag_name)
1014
908
yield (ref_name, tag_name, peeled, unpeeled)
1016
def set_last_revision_info(self, revno, revid):
1017
self.generate_revision_history(revid)
1019
def generate_revision_history(self, revision_id, last_rev=None,
1021
sha = self.lookup_bzr_revision_id(revision_id)[0]
1022
def get_changed_refs(old_refs):
1023
return {self.ref: sha}
1024
def generate_pack_data(have, want, ofs_delta=False):
1025
return pack_objects_to_data([])
1026
self.repository.send_pack(get_changed_refs, generate_pack_data)
1030
911
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):