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 lazy_check_versions
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,
27
61
lazy_check_versions()
29
from bzrlib.plugins.git.branch import GitBranch
30
from bzrlib.plugins.git.errors import NoSuchRef
31
from bzrlib.plugins.git.dir import GitDir
32
from bzrlib.plugins.git.foreign import ForeignBranch
33
from bzrlib.plugins.git.repository import GitFormat, GitRepository
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 (
41
from dulwich.pack import PackData, Pack, PackIndex
43
# Don't run any tests on GitSmartTransport as it is not intended to be
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
44
149
# a full implementation of Transport
45
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 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
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
215
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
216
# Don't know, just return it to the user as-is
217
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'))
49
243
class GitSmartTransport(Transport):
51
245
def __init__(self, url, _client=None):
52
246
Transport.__init__(self, url)
53
(scheme, _, loc, _, _) = urlparse.urlsplit(url)
54
assert scheme == "git"
55
hostport, self._path = urllib.splithost(loc)
56
(self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
247
(self._host, self._port, self._username, self._path) = \
249
if 'transport' in debug.debug_flags:
250
trace.mutter('host: %r, user: %r, port: %r, path: %r',
251
self._host, self._username, self._port, self._path)
57
252
self._client = _client
253
self._stripped_path = self._path.rsplit(",", 1)[0]
255
def external_url(self):
258
def has(self, relpath):
59
261
def _get_client(self):
60
if self._client is not None:
64
return git.client.TCPGitClient(self._host, self._port,
65
capabilities=["multi_ack", "side-band-64k", "ofs-delta", "side-band"])
262
raise NotImplementedError(self._get_client)
67
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
70
info("git: %s" % text)
71
self._get_client().fetch_pack(self._path, determine_wants,
72
graph_walker, pack_data, progress)
265
return self._stripped_path
74
267
def get(self, path):
75
268
raise NoSuchFile(path)
85
278
newurl = urlutils.join(self.base, offset)
87
return GitSmartTransport(newurl, self._client)
280
return self.__class__(newurl, self._client)
283
class TCPGitSmartTransport(GitSmartTransport):
287
def _get_client(self):
288
if self._client is not None:
293
# return dulwich.client.LocalGitClient()
294
return dulwich.client.SubprocessGitClient()
295
return dulwich.client.TCPGitClient(
296
self._host, self._port, report_activity=self._report_activity)
299
class SSHSocketWrapper(object):
301
def __init__(self, sock):
304
def read(self, len=None):
305
return self.sock.recv(len)
307
def write(self, data):
308
return self.sock.write(data)
311
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
314
class DulwichSSHVendor(dulwich.client.SSHVendor):
317
from ..transport import ssh
318
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
320
def run_command(self, host, command, username=None, port=None):
321
connection = self.bzr_ssh_vendor.connect_ssh(
322
username=username, password=None, port=port, host=host,
324
(kind, io_object) = connection.get_sock_or_pipes()
326
return SSHSocketWrapper(io_object)
328
raise AssertionError("Unknown io object kind %r'" % kind)
331
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
334
class SSHGitSmartTransport(GitSmartTransport):
339
path = self._stripped_path
340
if path.startswith("/~/"):
344
def _get_client(self):
345
if self._client is not None:
349
location_config = config.LocationConfig(self.base)
350
client = dulwich.client.SSHGitClient(
351
self._host, self._port, self._username,
352
report_activity=self._report_activity)
353
# Set up alternate pack program paths
354
upload_pack = location_config.get_user_option('git_upload_pack')
356
client.alternative_paths["upload-pack"] = upload_pack
357
receive_pack = location_config.get_user_option('git_receive_pack')
359
client.alternative_paths["receive-pack"] = receive_pack
363
class RemoteGitBranchFormat(GitBranchFormat):
365
def get_format_description(self):
366
return 'Remote Git Branch'
369
def _matchingcontroldir(self):
370
return RemoteGitControlDirFormat()
372
def initialize(self, a_controldir, name=None, repository=None,
373
append_revisions_only=None):
374
raise UninitializableFormat(self)
377
class DefaultProgressReporter(object):
379
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
380
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
382
def __init__(self, pb):
385
def progress(self, text):
386
text = text.rstrip(b"\r\n")
387
text = text.decode('utf-8')
388
if text.lower().startswith('error: '):
389
trace.show_error('git: %s', text[len(b'error: '):])
391
trace.mutter("git: %s", text)
392
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
394
(text, pct, current, total) = g.groups()
395
self.pb.update(text, int(current), int(total))
397
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
399
(text, total) = g.groups()
400
self.pb.update(text, None, int(total))
402
trace.note("%s", text)
90
405
class RemoteGitDir(GitDir):
92
def __init__(self, transport, lockfiles, format):
407
def __init__(self, transport, format, client, client_path):
93
408
self._format = format
94
409
self.root_transport = transport
95
410
self.transport = transport
96
self._lockfiles = lockfiles
411
self._mode_check_done = None
412
self._client = client
413
self._client_path = client_path
414
self.base = self.root_transport.base
418
def _gitrepository_class(self):
419
return RemoteGitRepository
421
def archive(self, format, committish, write_data, progress=None,
422
write_error=None, subdirs=None, prefix=None):
424
pb = ui.ui_factory.nested_progress_bar()
425
progress = DefaultProgressReporter(pb).progress
428
def progress_wrapper(message):
429
if message.startswith(b"fatal: Unknown archive format \'"):
430
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
431
raise errors.NoSuchExportFormat(format.decode('ascii'))
432
return progress(message)
434
self._client.archive(
435
self._client_path, committish, write_data, progress_wrapper,
437
format=(format.encode('ascii') if format else None),
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)
442
except GitProtocolError as e:
443
raise parse_git_error(self.transport.external_url(), e)
448
def fetch_pack(self, determine_wants, graph_walker, pack_data,
451
pb = ui.ui_factory.nested_progress_bar()
452
progress = DefaultProgressReporter(pb).progress
456
result = self._client.fetch_pack(
457
self._client_path, determine_wants, graph_walker, pack_data,
459
if result.refs is None:
461
self._refs = remote_refs_dict_to_container(
462
result.refs, result.symrefs)
464
except HangupException as e:
465
raise parse_git_hangup(self.transport.external_url(), e)
466
except GitProtocolError as e:
467
raise parse_git_error(self.transport.external_url(), e)
472
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
474
pb = ui.ui_factory.nested_progress_bar()
475
progress = DefaultProgressReporter(pb).progress
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)
484
return self._client.send_pack(
485
self._client_path, get_changed_refs_wrapper,
486
generate_pack_data, progress)
487
except HangupException as e:
488
raise parse_git_hangup(self.transport.external_url(), e)
489
except GitProtocolError as e:
490
raise parse_git_error(self.transport.external_url(), e)
495
def create_branch(self, name=None, repository=None,
496
append_revisions_only=None, ref=None):
497
refname = self._get_selected_ref(name, ref)
498
if refname != b'HEAD' and refname in self.get_refs_container():
499
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]
504
repo = self.open_repository()
505
return RemoteGitBranch(self, repo, refname)
507
def destroy_branch(self, name=None):
508
refname = self._get_selected_ref(name)
510
def get_changed_refs(old_refs):
512
if refname not in old_refs:
513
raise NotBranchError(self.user_url)
514
ret[refname] = dulwich.client.ZERO_SHA
517
def generate_pack_data(have, want, ofs_delta=False):
518
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)
523
raise RemoteGitError(error)
527
return self.control_url
530
def user_transport(self):
531
return self.root_transport
534
def control_url(self):
535
return self.control_transport.base
538
def control_transport(self):
539
return self.root_transport
98
541
def open_repository(self):
99
return RemoteGitRepository(self, self._lockfiles)
101
def open_branch(self, _unsupported=False):
542
return RemoteGitRepository(self)
544
def get_branch_reference(self, name=None):
545
ref = branch_name_to_ref(name)
546
val = self.get_refs_container().read_ref(ref)
547
if val.startswith(SYMREF):
548
return val[len(SYMREF):]
551
def open_branch(self, name=None, unsupported=False,
552
ignore_fallbacks=False, ref=None, possible_transports=None,
102
554
repo = self.open_repository()
103
# TODO: Support for multiple branches in one bzrdir in bzrlib!
104
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
555
ref = self._get_selected_ref(name, ref)
557
if not nascent_ok and ref not in self.get_refs_container():
558
raise NotBranchError(
559
self.root_transport.base, controldir=self)
560
except NotGitRepository:
561
raise NotBranchError(self.root_transport.base,
563
ref_chain, unused_sha = self.get_refs_container().follow(ref)
564
return RemoteGitBranch(self, repo, ref_chain[-1])
106
def open_workingtree(self):
566
def open_workingtree(self, recommend_upgrade=False):
107
567
raise NotLocalUrl(self.transport.base)
569
def has_workingtree(self):
572
def get_peeled(self, name):
573
return self.get_refs_container().get_peeled(name)
575
def get_refs_container(self):
576
if self._refs is not None:
578
result = self.fetch_pack(lambda x: None, None,
580
lambda x: trace.mutter("git: %s" % x))
581
self._refs = remote_refs_dict_to_container(
582
result.refs, result.symrefs)
585
def push_branch(self, source, revision_id=None, overwrite=False,
586
remember=False, create_prefix=False, lossy=False,
587
name=None, tag_selector=None):
588
"""Push the source branch into this ControlDir."""
589
if revision_id is None:
590
# No revision supplied by the user, default to the branch
592
revision_id = source.last_revision()
594
if not source.repository.has_revision(revision_id):
595
raise NoSuchRevision(source, revision_id)
597
push_result = GitPushResult()
598
push_result.workingtree_updated = None
599
push_result.master_branch = None
600
push_result.source_branch = source
601
push_result.stacked_on = None
602
push_result.branch_push_result = None
603
repo = self.find_repository()
604
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
if isinstance(source, GitBranch) and lossy:
616
raise errors.LossyPushToSameVCS(source.controldir, self)
617
source_store = get_object_store(source.repository)
618
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)
623
# TODO(jelmer): Unpeel if necessary
624
push_result.new_original_revid = revision_id
626
new_sha = source_store._lookup_revision_sha1(revision_id)
629
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
630
except errors.NoSuchRevision:
631
raise errors.NoRoundtrippingSupport(
632
source, self.open_branch(name=name, nascent_ok=True))
634
old_sha = remote_refs.get(actual_refname)
635
if remote_divergence(old_sha, new_sha, source_store):
636
raise DivergedBranches(
637
source, self.open_branch(name, nascent_ok=True))
638
ret[actual_refname] = new_sha
640
for tagname, revid in source.tags.get_tag_dict().items():
641
if tag_selector and not tag_selector(tagname):
645
new_sha = source_store._lookup_revision_sha1(revid)
647
if source.repository.has_revision(revid):
651
new_sha = repo.lookup_bzr_revision_id(revid)[0]
652
except errors.NoSuchRevision:
655
if not source.repository.has_revision(revid):
657
ret[tag_name_to_ref(tagname)] = new_sha
659
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
690
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)
698
push_result.target_branch = self.open_branch(name)
699
if old_sha is not None:
700
push_result.branch_push_result = GitBranchPushResult()
701
push_result.branch_push_result.source_branch = source
702
push_result.branch_push_result.target_branch = (
703
push_result.target_branch)
704
push_result.branch_push_result.local_branch = None
705
push_result.branch_push_result.master_branch = (
706
push_result.target_branch)
707
push_result.branch_push_result.old_revid = push_result.old_revid
708
push_result.branch_push_result.new_revid = push_result.new_revid
709
push_result.branch_push_result.new_original_revid = (
710
push_result.new_original_revid)
711
if source.get_push_location() is None or remember:
712
source.set_push_location(push_result.target_branch.base)
715
def _find_commondir(self):
716
# There is no way to find the commondir, if there is any.
110
720
class EmptyObjectStoreIterator(dict):
116
726
class TemporaryPackIterator(Pack):
118
728
def __init__(self, path, resolve_ext_ref):
119
self.resolve_ext_ref = resolve_ext_ref
120
super(TemporaryPackIterator, self).__init__(path)
729
super(TemporaryPackIterator, self).__init__(
730
path, resolve_ext_ref=resolve_ext_ref)
731
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
124
if self._idx is None:
125
self._data.create_index_v2(self._idx_path, self.resolve_ext_ref)
126
self._idx = PackIndex(self._idx_path)
733
def _idx_load_or_generate(self, path):
734
if not os.path.exists(path):
735
with ui.ui_factory.nested_progress_bar() as pb:
736
def report_progress(cur, total):
737
pb.update("generating index", cur, total)
738
self.data.create_index(path, progress=report_progress)
739
return load_pack_index(path)
129
741
def __del__(self):
130
os.remove(self._data_path)
131
os.remove(self._idx_path)
742
if self._idx is not None:
744
os.remove(self._idx_path)
745
if self._data is not None:
747
os.remove(self._data_path)
750
class BzrGitHttpClient(dulwich.client.HttpGitClient):
752
def __init__(self, transport, *args, **kwargs):
753
self.transport = transport
754
url = urlutils.URL.from_string(transport.external_url())
755
url.user = url.quoted_user = None
756
url.password = url.quoted_password = None
757
url = urlutils.strip_segment_parameters(str(url))
758
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
760
def _http_request(self, url, headers=None, data=None,
761
allow_compression=False):
762
"""Perform HTTP request.
764
:param url: Request URL.
765
:param headers: Optional custom headers to override defaults.
766
:param data: Request data.
767
:param allow_compression: Allow GZipped communication.
768
:return: Tuple (`response`, `read`), where response is an `urllib3`
769
response object with additional `content_type` and
770
`redirect_location` properties, and `read` is a consumable read
771
method for the response data.
773
if is_github_url(url):
774
headers['User-agent'] = user_agent_for_github()
775
headers["Pragma"] = "no-cache"
776
if allow_compression:
777
headers["Accept-Encoding"] = "gzip"
779
headers["Accept-Encoding"] = "identity"
781
response = self.transport.request(
782
('GET' if data is None else 'POST'),
785
headers=headers, retries=8)
787
if response.status == 404:
788
raise NotGitRepository()
789
elif response.status != 200:
790
raise GitProtocolError("unexpected http resp %d for %s" %
791
(response.status, url))
793
# TODO: Optimization available by adding `preload_content=False` to the
794
# request and just passing the `read` method on instead of going via
795
# `BytesIO`, if we can guarantee that the entire response is consumed
796
# before issuing the next to still allow for connection reuse from the
798
if response.getheader("Content-Encoding") == "gzip":
799
read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
803
class WrapResponse(object):
805
def __init__(self, response):
806
self._response = response
807
self.status = response.status
808
self.content_type = response.getheader("Content-Type")
809
self.redirect_location = response._actual.geturl()
812
return self._response.readlines()
817
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
class RemoteGitControlDirFormat(GitControlDirFormat):
826
"""The .git directory control format."""
828
supports_workingtrees = False
831
def _known_formats(self):
832
return set([RemoteGitControlDirFormat()])
834
def get_branch_format(self):
835
return RemoteGitBranchFormat()
838
def repository_format(self):
839
return GitRepositoryFormat()
841
def is_initializable(self):
844
def is_supported(self):
847
def open(self, transport, _found=None):
848
"""Open this directory.
851
split_url = _git_url_and_path_from_transport(transport.external_url())
852
if isinstance(transport, GitSmartTransport):
853
client = transport._get_client()
854
elif split_url.scheme in ("http", "https"):
855
client = BzrGitHttpClient(transport)
856
elif split_url.scheme in ('file', ):
857
client = dulwich.client.LocalGitClient()
859
raise NotBranchError(transport.base)
861
pass # TODO(jelmer): Actually probe for something
862
return RemoteGitDir(transport, self, client, split_url.path)
864
def get_format_description(self):
865
return "Remote Git Repository"
867
def initialize_on_transport(self, transport):
868
raise UninitializableFormat(self)
870
def supports_transport(self, transport):
872
external_url = transport.external_url()
873
except InProcessTransport:
874
raise NotBranchError(path=transport.base)
875
return (external_url.startswith("http:")
876
or external_url.startswith("https:")
877
or external_url.startswith("git+")
878
or external_url.startswith("git:"))
881
class GitRemoteRevisionTree(RevisionTree):
883
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
884
"""Create an archive of this tree.
886
:param format: Format name (e.g. 'tar')
887
:param name: target file name
888
:param root: Root directory name (or None)
889
:param subdir: Subdirectory to export (or None)
890
:return: Iterator over archive chunks
892
commit = self._repository.lookup_bzr_revision_id(
893
self.get_revision_id())[0]
895
f = tempfile.SpooledTemporaryFile()
896
# git-upload-archive(1) generaly only supports refs. So let's see if we
900
self._repository.controldir.get_refs_container().as_dict().items()}
902
committish = reverse_refs[commit]
904
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
905
# Let's hope for the best.
907
self._repository.archive(
908
format, committish, f.write,
909
subdirs=([subdir] if subdir else None),
910
prefix=(root + '/') if root else '')
912
return osutils.file_iterator(f)
914
def is_versioned(self, path):
915
raise GitSmartRemoteNotSupported(self.is_versioned, self)
917
def has_filename(self, path):
918
raise GitSmartRemoteNotSupported(self.has_filename, self)
920
def get_file_text(self, path):
921
raise GitSmartRemoteNotSupported(self.get_file_text, self)
923
def list_files(self, include_root=False, from_dir=None, recursive=True):
924
raise GitSmartRemoteNotSupported(self.list_files, self)
134
927
class RemoteGitRepository(GitRepository):
136
def __init__(self, gitdir, lockfiles):
137
GitRepository.__init__(self, gitdir, lockfiles)
139
def fetch_pack(self, determine_wants, graph_walker, pack_data,
929
supports_random_access = False
933
return self.control_url
935
def get_parent_map(self, revids):
936
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
938
def archive(self, *args, **kwargs):
939
return self.controldir.archive(*args, **kwargs)
941
def fetch_pack(self, determine_wants, graph_walker, pack_data,
141
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
144
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref, progress=None):
943
return self.controldir.fetch_pack(
944
determine_wants, graph_walker, pack_data, progress)
946
def send_pack(self, get_changed_refs, generate_pack_data):
947
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
949
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
145
952
fd, path = tempfile.mkstemp(suffix=".pack")
146
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
954
self.fetch_pack(determine_wants, graph_walker,
955
lambda x: os.write(fd, x), progress)
148
958
if os.path.getsize(path) == 0:
149
959
return EmptyObjectStoreIterator()
150
960
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
962
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
963
# This won't work for any round-tripped bzr revisions, but it's a
966
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
967
except InvalidRevisionId:
968
raise NoSuchRevision(self, bzr_revid)
970
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
971
"""Lookup a revision id.
975
mapping = self.get_mapping()
976
# Not really an easy way to parse foreign revids here..
977
return mapping.revision_id_foreign_to_bzr(foreign_revid)
979
def revision_tree(self, revid):
980
return GitRemoteRevisionTree(self, revid)
982
def get_revisions(self, revids):
983
raise GitSmartRemoteNotSupported(self.get_revisions, self)
985
def has_revisions(self, revids):
986
raise GitSmartRemoteNotSupported(self.get_revisions, self)
989
class RemoteGitTagDict(GitTags):
991
def set_tag(self, name, revid):
992
sha = self.branch.lookup_bzr_revision_id(revid)[0]
993
self._set_ref(name, sha)
995
def delete_tag(self, name):
996
self._set_ref(name, dulwich.client.ZERO_SHA)
998
def _set_ref(self, name, sha):
999
ref = tag_name_to_ref(name)
1001
def get_changed_refs(old_refs):
1003
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
1004
raise NoSuchTag(name)
1008
def generate_pack_data(have, want, ofs_delta=False):
1009
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)
153
1018
class RemoteGitBranch(GitBranch):
155
def __init__(self, bzrdir, repository, name, lockfiles):
156
def determine_wants(heads):
157
if not name in heads:
158
raise NoSuchRef(name)
159
self._ref = heads[name]
160
bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None,
161
lambda x: mutter("git: %s" % x))
162
super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
1020
def __init__(self, controldir, repository, name):
1022
super(RemoteGitBranch, self).__init__(controldir, repository, name,
1023
RemoteGitBranchFormat())
1025
def last_revision_info(self):
1026
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
1030
return self.control_url
1033
def control_url(self):
1036
def revision_id_to_revno(self, revision_id):
1037
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
164
1039
def last_revision(self):
165
return self.mapping.revision_id_foreign_to_bzr(self._ref)
1040
return self.lookup_foreign_revision_id(self.head)
1044
if self._sha is not None:
1046
refs = self.controldir.get_refs_container()
1047
name = branch_name_to_ref(self.name)
1049
self._sha = refs[name]
1051
raise NoSuchRef(name, self.repository.user_url, refs)
167
1054
def _synchronize_history(self, destination, revision_id):
168
1055
"""See Branch._synchronize_history()."""
169
destination.generate_revision_history(self.last_revision())
1056
if revision_id is None:
1057
revision_id = self.last_revision()
1058
destination.generate_revision_history(revision_id)
1060
def _get_parent_location(self):
1063
def get_push_location(self):
1066
def set_push_location(self, url):
1069
def _iter_tag_refs(self):
1070
"""Iterate over the tag refs.
1072
:param refs: Refs dictionary (name -> git sha1)
1073
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
1075
refs = self.controldir.get_refs_container()
1076
for ref_name, unpeeled in refs.as_dict().items():
1078
tag_name = ref_to_tag_name(ref_name)
1079
except (ValueError, UnicodeDecodeError):
1081
peeled = refs.get_peeled(ref_name)
1083
# Let's just hope it's a commit
1085
if not isinstance(tag_name, str):
1086
raise TypeError(tag_name)
1087
yield (ref_name, tag_name, peeled, unpeeled)
1089
def set_last_revision_info(self, revno, revid):
1090
self.generate_revision_history(revid)
1092
def generate_revision_history(self, revision_id, last_rev=None,
1094
sha = self.lookup_bzr_revision_id(revision_id)[0]
1095
def get_changed_refs(old_refs):
1096
return {self.ref: sha}
1097
def generate_pack_data(have, want, ofs_delta=False):
1098
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)
1108
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1111
for k, v in refs_dict.items():
1116
for name, target in symrefs_dict.items():
1117
base[name] = SYMREF + target
1118
ret = DictRefsContainer(base)
1119
ret._peeled = peeled
1123
def update_refs_container(container, refs_dict):
1126
for k, v in refs_dict.items():
1131
container._peeled = peeled
1132
container._refs.update(base)