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
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
26
from bzrlib.plugins.git import git
27
from bzrlib.plugins.git.branch import GitBranch
28
from bzrlib.plugins.git.dir import GitDir
29
from bzrlib.plugins.git.foreign import ForeignBranch
30
from bzrlib.plugins.git.repository import GitFormat, GitRepository
35
from dulwich.pack import PackData
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Remote dirs, repositories and branches."""
20
from io import BytesIO
35
from ..errors import (
47
UninitializableFormat,
49
from ..revision import NULL_REVISION
50
from ..revisiontree import RevisionTree
51
from ..transport import (
53
register_urlparse_netloc_protocol,
59
user_agent_for_github,
75
GitSmartRemoteNotSupported,
78
from .mapping import (
82
from .object_store import (
88
from .repository import (
100
import dulwich.client
101
from dulwich.errors import (
105
from dulwich.pack import (
107
pack_objects_to_data,
109
from dulwich.protocol import ZERO_SHA
110
from dulwich.refs import (
114
from dulwich.repo import (
120
import urllib.parse as urlparse
121
from urllib.parse import splituser
123
# urlparse only supports a limited number of schemes by default
124
register_urlparse_netloc_protocol('git')
125
register_urlparse_netloc_protocol('git+ssh')
127
from dulwich.pack import load_pack_index
130
class GitPushResult(PushResult):
132
def _lookup_revno(self, revid):
134
return _quick_lookup_revno(self.source_branch, self.target_branch,
136
except GitSmartRemoteNotSupported:
141
return self._lookup_revno(self.old_revid)
145
return self._lookup_revno(self.new_revid)
148
# Don't run any tests on GitSmartTransport as it is not intended to be
149
# a full implementation of Transport
150
def get_test_permutations():
154
def split_git_url(url):
158
:return: Tuple with host, port, username, path.
160
parsed_url = urlparse.urlparse(url)
161
path = urlparse.unquote(parsed_url.path)
162
if path.startswith("/~"):
164
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
167
class RemoteGitError(BzrError):
169
_fmt = "Remote server error: %(msg)s"
172
class HeadUpdateFailed(BzrError):
174
_fmt = ("Unable to update remote HEAD branch. To update the master "
175
"branch, specify the URL %(base_url)s,branch=master.")
177
def __init__(self, base_url):
178
super(HeadUpdateFailed, self).__init__()
179
self.base_url = base_url
182
def parse_git_error(url, message):
183
"""Parse a remote git server error and return a bzr exception.
185
:param url: URL of the remote repository
186
:param message: Message sent by the remote git server
188
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.'))):
193
return NotBranchError(url, message)
194
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))
214
# Don't know, just return it to the user as-is
215
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'))
38
241
class GitSmartTransport(Transport):
40
243
def __init__(self, url, _client=None):
41
244
Transport.__init__(self, url)
42
(scheme, _, loc, _, _) = urlparse.urlsplit(url)
43
assert scheme == "git"
44
hostport, self._path = urllib.splithost(loc)
45
(self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
46
if _client is not None:
47
self._client = _client
49
self._client = git.client.TCPGitClient(self._host, self._port)
51
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
54
info("git: %s" % text)
55
self._client.fetch_pack(self._path, determine_wants, graph_walker,
58
def fetch_objects(self, determine_wants, graph_walker, progress=None):
59
fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
60
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
64
for o in p.iterobjects():
245
(self._host, self._port, self._username, self._path) = \
247
if 'transport' in debug.debug_flags:
248
trace.mutter('host: %r, user: %r, port: %r, path: %r',
249
self._host, self._username, self._port, self._path)
250
self._client = _client
251
self._stripped_path = self._path.rsplit(",", 1)[0]
253
def external_url(self):
256
def has(self, relpath):
259
def _get_client(self):
260
raise NotImplementedError(self._get_client)
263
return self._stripped_path
69
265
def get(self, path):
70
266
raise NoSuchFile(path)
268
def abspath(self, relpath):
269
return urlutils.join(self.base, relpath)
72
271
def clone(self, offset=None):
73
272
"""See Transport.clone()."""
74
273
if offset is None:
77
276
newurl = urlutils.join(self.base, offset)
79
return GitSmartTransport(newurl, self._client)
278
return self.__class__(newurl, self._client)
281
class TCPGitSmartTransport(GitSmartTransport):
285
def _get_client(self):
286
if self._client is not None:
291
# return dulwich.client.LocalGitClient()
292
return dulwich.client.SubprocessGitClient()
293
return dulwich.client.TCPGitClient(
294
self._host, self._port, report_activity=self._report_activity)
297
class SSHSocketWrapper(object):
299
def __init__(self, sock):
302
def read(self, len=None):
303
return self.sock.recv(len)
305
def write(self, data):
306
return self.sock.write(data)
309
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
312
class DulwichSSHVendor(dulwich.client.SSHVendor):
315
from ..transport import ssh
316
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
318
def run_command(self, host, command, username=None, port=None):
319
connection = self.bzr_ssh_vendor.connect_ssh(
320
username=username, password=None, port=port, host=host,
322
(kind, io_object) = connection.get_sock_or_pipes()
324
return SSHSocketWrapper(io_object)
326
raise AssertionError("Unknown io object kind %r'" % kind)
329
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
332
class SSHGitSmartTransport(GitSmartTransport):
337
path = self._stripped_path
338
if path.startswith("/~/"):
342
def _get_client(self):
343
if self._client is not None:
347
location_config = config.LocationConfig(self.base)
348
client = dulwich.client.SSHGitClient(
349
self._host, self._port, self._username,
350
report_activity=self._report_activity)
351
# Set up alternate pack program paths
352
upload_pack = location_config.get_user_option('git_upload_pack')
354
client.alternative_paths["upload-pack"] = upload_pack
355
receive_pack = location_config.get_user_option('git_receive_pack')
357
client.alternative_paths["receive-pack"] = receive_pack
361
class RemoteGitBranchFormat(GitBranchFormat):
363
def get_format_description(self):
364
return 'Remote Git Branch'
367
def _matchingcontroldir(self):
368
return RemoteGitControlDirFormat()
370
def initialize(self, a_controldir, name=None, repository=None,
371
append_revisions_only=None):
372
raise UninitializableFormat(self)
375
class DefaultProgressReporter(object):
377
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
378
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
380
def __init__(self, pb):
383
def progress(self, text):
384
text = text.rstrip(b"\r\n")
385
text = text.decode('utf-8')
386
if text.lower().startswith('error: '):
387
trace.show_error('git: %s', text[len(b'error: '):])
389
trace.mutter("git: %s", text)
390
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
392
(text, pct, current, total) = g.groups()
393
self.pb.update(text, int(current), int(total))
395
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
397
(text, total) = g.groups()
398
self.pb.update(text, None, int(total))
400
trace.note("%s", text)
82
403
class RemoteGitDir(GitDir):
84
def __init__(self, transport, lockfiles, format):
405
def __init__(self, transport, format, client, client_path):
85
406
self._format = format
86
407
self.root_transport = transport
87
408
self.transport = transport
88
self._lockfiles = lockfiles
409
self._mode_check_done = None
410
self._client = client
411
self._client_path = client_path
412
self.base = self.root_transport.base
416
def _gitrepository_class(self):
417
return RemoteGitRepository
419
def archive(self, format, committish, write_data, progress=None,
420
write_error=None, subdirs=None, prefix=None):
422
pb = ui.ui_factory.nested_progress_bar()
423
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)
440
except GitProtocolError as e:
441
raise parse_git_error(self.transport.external_url(), e)
446
def fetch_pack(self, determine_wants, graph_walker, pack_data,
449
pb = ui.ui_factory.nested_progress_bar()
450
progress = DefaultProgressReporter(pb).progress
454
result = self._client.fetch_pack(
455
self._client_path, determine_wants, graph_walker, pack_data,
457
if result.refs is None:
459
self._refs = remote_refs_dict_to_container(
460
result.refs, result.symrefs)
462
except HangupException as e:
463
raise parse_git_hangup(self.transport.external_url(), e)
464
except GitProtocolError as e:
465
raise parse_git_error(self.transport.external_url(), e)
470
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
472
pb = ui.ui_factory.nested_progress_bar()
473
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)
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)
487
except GitProtocolError as e:
488
raise parse_git_error(self.transport.external_url(), e)
493
def create_branch(self, name=None, repository=None,
494
append_revisions_only=None, ref=None):
495
refname = self._get_selected_ref(name, ref)
496
if refname != b'HEAD' and refname in self.get_refs_container():
497
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]
502
repo = self.open_repository()
503
return RemoteGitBranch(self, repo, refname)
505
def destroy_branch(self, name=None):
506
refname = self._get_selected_ref(name)
508
def get_changed_refs(old_refs):
510
if refname not in old_refs:
511
raise NotBranchError(self.user_url)
512
ret[refname] = dulwich.client.ZERO_SHA
515
def generate_pack_data(have, want, ofs_delta=False):
516
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)
525
return self.control_url
528
def user_transport(self):
529
return self.root_transport
532
def control_url(self):
533
return self.control_transport.base
536
def control_transport(self):
537
return self.root_transport
90
539
def open_repository(self):
91
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
540
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
def open_branch(self, name=None, unsupported=False,
550
ignore_fallbacks=False, ref=None, possible_transports=None,
94
552
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
553
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:
559
raise NotBranchError(self.root_transport.base,
561
ref_chain, unused_sha = self.get_refs_container().follow(ref)
562
return RemoteGitBranch(self, repo, ref_chain[-1])
98
def open_workingtree(self):
564
def open_workingtree(self, recommend_upgrade=False):
99
565
raise NotLocalUrl(self.transport.base)
567
def has_workingtree(self):
570
def get_peeled(self, name):
571
return self.get_refs_container().get_peeled(name)
573
def get_refs_container(self):
574
if self._refs is not None:
576
result = self.fetch_pack(lambda x: None, None,
578
lambda x: trace.mutter("git: %s" % x))
579
self._refs = remote_refs_dict_to_container(
580
result.refs, result.symrefs)
583
def push_branch(self, source, revision_id=None, overwrite=False,
584
remember=False, create_prefix=False, lossy=False,
585
name=None, tag_selector=None):
586
"""Push the source branch into this ControlDir."""
587
if revision_id is None:
588
# No revision supplied by the user, default to the branch
590
revision_id = source.last_revision()
592
if not source.repository.has_revision(revision_id):
593
raise NoSuchRevision(source, revision_id)
595
push_result = GitPushResult()
596
push_result.workingtree_updated = None
597
push_result.master_branch = None
598
push_result.source_branch = source
599
push_result.stacked_on = None
600
push_result.branch_push_result = None
601
repo = self.find_repository()
602
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
if isinstance(source, GitBranch) and lossy:
609
raise errors.LossyPushToSameVCS(source.controldir, self)
610
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
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()
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)
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
682
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)
690
push_result.target_branch = self.open_branch(name)
691
if old_sha is not None:
692
push_result.branch_push_result = GitBranchPushResult()
693
push_result.branch_push_result.source_branch = source
694
push_result.branch_push_result.target_branch = (
695
push_result.target_branch)
696
push_result.branch_push_result.local_branch = None
697
push_result.branch_push_result.master_branch = (
698
push_result.target_branch)
699
push_result.branch_push_result.old_revid = push_result.old_revid
700
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)
703
if source.get_push_location() is None or remember:
704
source.set_push_location(push_result.target_branch.base)
707
def _find_commondir(self):
708
# There is no way to find the commondir, if there is any.
712
class EmptyObjectStoreIterator(dict):
714
def iterobjects(self):
718
class TemporaryPackIterator(Pack):
720
def __init__(self, path, resolve_ext_ref):
721
super(TemporaryPackIterator, self).__init__(
722
path, resolve_ext_ref=resolve_ext_ref)
723
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
725
def _idx_load_or_generate(self, path):
726
if not os.path.exists(path):
727
with ui.ui_factory.nested_progress_bar() as pb:
728
def report_progress(cur, total):
729
pb.update("generating index", cur, total)
730
self.data.create_index(path, progress=report_progress)
731
return load_pack_index(path)
734
if self._idx is not None:
736
os.remove(self._idx_path)
737
if self._data is not None:
739
os.remove(self._data_path)
742
class BzrGitHttpClient(dulwich.client.HttpGitClient):
744
def __init__(self, transport, *args, **kwargs):
745
self.transport = transport
746
url = urlutils.URL.from_string(transport.external_url())
747
url.user = url.quoted_user = None
748
url.password = url.quoted_password = None
749
url = urlutils.strip_segment_parameters(str(url))
750
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
752
def _http_request(self, url, headers=None, data=None,
753
allow_compression=False):
754
"""Perform HTTP request.
756
:param url: Request URL.
757
:param headers: Optional custom headers to override defaults.
758
:param data: Request data.
759
:param allow_compression: Allow GZipped communication.
760
:return: Tuple (`response`, `read`), where response is an `urllib3`
761
response object with additional `content_type` and
762
`redirect_location` properties, and `read` is a consumable read
763
method for the response data.
765
if is_github_url(url):
766
headers['User-agent'] = user_agent_for_github()
767
headers["Pragma"] = "no-cache"
768
if allow_compression:
769
headers["Accept-Encoding"] = "gzip"
771
headers["Accept-Encoding"] = "identity"
773
response = self.transport.request(
774
('GET' if data is None else 'POST'),
777
headers=headers, retries=8)
779
if response.status == 404:
780
raise NotGitRepository()
781
elif response.status != 200:
782
raise GitProtocolError("unexpected http resp %d for %s" %
783
(response.status, url))
785
# TODO: Optimization available by adding `preload_content=False` to the
786
# request and just passing the `read` method on instead of going via
787
# `BytesIO`, if we can guarantee that the entire response is consumed
788
# before issuing the next to still allow for connection reuse from the
790
if response.getheader("Content-Encoding") == "gzip":
791
read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
795
class WrapResponse(object):
797
def __init__(self, response):
798
self._response = response
799
self.status = response.status
800
self.content_type = response.getheader("Content-Type")
801
self.redirect_location = response._actual.geturl()
804
return self._response.readlines()
809
return WrapResponse(response), read
812
def _git_url_and_path_from_transport(external_url):
813
url = urlutils.strip_segment_parameters(external_url)
814
return urlparse.urlsplit(url)
817
class RemoteGitControlDirFormat(GitControlDirFormat):
818
"""The .git directory control format."""
820
supports_workingtrees = False
823
def _known_formats(self):
824
return set([RemoteGitControlDirFormat()])
826
def get_branch_format(self):
827
return RemoteGitBranchFormat()
830
def repository_format(self):
831
return GitRepositoryFormat()
833
def is_initializable(self):
836
def is_supported(self):
839
def open(self, transport, _found=None):
840
"""Open this directory.
843
split_url = _git_url_and_path_from_transport(transport.external_url())
844
if isinstance(transport, GitSmartTransport):
845
client = transport._get_client()
846
elif split_url.scheme in ("http", "https"):
847
client = BzrGitHttpClient(transport)
848
elif split_url.scheme in ('file', ):
849
client = dulwich.client.LocalGitClient()
851
raise NotBranchError(transport.base)
853
pass # TODO(jelmer): Actually probe for something
854
return RemoteGitDir(transport, self, client, split_url.path)
856
def get_format_description(self):
857
return "Remote Git Repository"
859
def initialize_on_transport(self, transport):
860
raise UninitializableFormat(self)
862
def supports_transport(self, transport):
864
external_url = transport.external_url()
865
except InProcessTransport:
866
raise NotBranchError(path=transport.base)
867
return (external_url.startswith("http:")
868
or external_url.startswith("https:")
869
or external_url.startswith("git+")
870
or external_url.startswith("git:"))
873
class GitRemoteRevisionTree(RevisionTree):
875
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
876
"""Create an archive of this tree.
878
:param format: Format name (e.g. 'tar')
879
:param name: target file name
880
:param root: Root directory name (or None)
881
:param subdir: Subdirectory to export (or None)
882
:return: Iterator over archive chunks
884
commit = self._repository.lookup_bzr_revision_id(
885
self.get_revision_id())[0]
887
f = tempfile.SpooledTemporaryFile()
888
# git-upload-archive(1) generaly only supports refs. So let's see if we
892
self._repository.controldir.get_refs_container().as_dict().items()}
894
committish = reverse_refs[commit]
896
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
897
# Let's hope for the best.
899
self._repository.archive(
900
format, committish, f.write,
901
subdirs=([subdir] if subdir else None),
902
prefix=(root + '/') if root else '')
904
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)
102
919
class RemoteGitRepository(GitRepository):
104
def __init__(self, gitdir, lockfiles):
105
GitRepository.__init__(self, gitdir, lockfiles)
107
def fetch_pack(self, determine_wants, graph_walker, pack_data,
921
supports_random_access = False
925
return self.control_url
927
def get_parent_map(self, revids):
928
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
930
def archive(self, *args, **kwargs):
931
return self.controldir.archive(*args, **kwargs)
933
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
935
return self.controldir.fetch_pack(
936
determine_wants, graph_walker, pack_data, progress)
938
def send_pack(self, get_changed_refs, generate_pack_data):
939
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
941
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
944
fd, path = tempfile.mkstemp(suffix=".pack")
946
self.fetch_pack(determine_wants, graph_walker,
947
lambda x: os.write(fd, x), progress)
950
if os.path.getsize(path) == 0:
951
return EmptyObjectStoreIterator()
952
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
954
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
955
# This won't work for any round-tripped bzr revisions, but it's a
958
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
959
except InvalidRevisionId:
960
raise NoSuchRevision(self, bzr_revid)
962
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
963
"""Lookup a revision id.
967
mapping = self.get_mapping()
968
# Not really an easy way to parse foreign revids here..
969
return mapping.revision_id_foreign_to_bzr(foreign_revid)
971
def revision_tree(self, revid):
972
return GitRemoteRevisionTree(self, revid)
974
def get_revisions(self, revids):
975
raise GitSmartRemoteNotSupported(self.get_revisions, self)
977
def has_revisions(self, revids):
978
raise GitSmartRemoteNotSupported(self.get_revisions, self)
981
class RemoteGitTagDict(GitTags):
983
def set_tag(self, name, revid):
984
sha = self.branch.lookup_bzr_revision_id(revid)[0]
985
self._set_ref(name, sha)
987
def delete_tag(self, name):
988
self._set_ref(name, dulwich.client.ZERO_SHA)
990
def _set_ref(self, name, sha):
991
ref = tag_name_to_ref(name)
993
def get_changed_refs(old_refs):
995
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
996
raise NoSuchTag(name)
1000
def generate_pack_data(have, want, ofs_delta=False):
1001
return pack_objects_to_data([])
1002
result = self.repository.send_pack(
1003
get_changed_refs, generate_pack_data)
1004
if result and not isinstance(result, dict):
1005
error = result.ref_status.get(ref)
1007
raise RemoteGitError(error)
113
1010
class RemoteGitBranch(GitBranch):
115
def __init__(self, bzrdir, repository, name, lockfiles):
116
def determine_wants(heads):
117
self._ref = heads[name]
118
bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None,
119
lambda x: mutter("git: %s" % x))
120
super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
1012
def __init__(self, controldir, repository, name):
1014
super(RemoteGitBranch, self).__init__(controldir, repository, name,
1015
RemoteGitBranchFormat())
1017
def last_revision_info(self):
1018
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
1022
return self.control_url
1025
def control_url(self):
1028
def revision_id_to_revno(self, revision_id):
1029
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
1031
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
1032
return self.lookup_foreign_revision_id(self.head)
1036
if self._sha is not None:
1038
refs = self.controldir.get_refs_container()
1039
name = branch_name_to_ref(self.name)
1041
self._sha = refs[name]
1043
raise NoSuchRef(name, self.repository.user_url, refs)
1046
def _synchronize_history(self, destination, revision_id):
1047
"""See Branch._synchronize_history()."""
1048
if revision_id is None:
1049
revision_id = self.last_revision()
1050
destination.generate_revision_history(revision_id)
1052
def _get_parent_location(self):
1055
def get_push_location(self):
1058
def set_push_location(self, url):
1061
def _iter_tag_refs(self):
1062
"""Iterate over the tag refs.
1064
:param refs: Refs dictionary (name -> git sha1)
1065
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
1067
refs = self.controldir.get_refs_container()
1068
for ref_name, unpeeled in refs.as_dict().items():
1070
tag_name = ref_to_tag_name(ref_name)
1071
except (ValueError, UnicodeDecodeError):
1073
peeled = refs.get_peeled(ref_name)
1075
# Let's just hope it's a commit
1077
if not isinstance(tag_name, str):
1078
raise TypeError(tag_name)
1079
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
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1103
for k, v in refs_dict.items():
1108
for name, target in symrefs_dict.items():
1109
base[name] = SYMREF + target
1110
ret = DictRefsContainer(base)
1111
ret._peeled = peeled
1115
def update_refs_container(container, refs_dict):
1118
for k, v in refs_dict.items():
1123
container._peeled = peeled
1124
container._refs.update(base)