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 (
81
from .object_store import (
87
from .repository import (
100
from dulwich.errors import (
104
from dulwich.pack import (
106
pack_objects_to_data,
108
from dulwich.protocol import ZERO_SHA
109
from dulwich.refs import (
113
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)
207
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
209
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
210
# Don't know, just return it to the user as-is
211
return RemoteGitError(message)
38
214
class GitSmartTransport(Transport):
40
216
def __init__(self, url, _client=None):
41
217
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():
218
(self._host, self._port, self._username, self._path) = \
220
if 'transport' in debug.debug_flags:
221
trace.mutter('host: %r, user: %r, port: %r, path: %r',
222
self._host, self._username, self._port, self._path)
223
self._client = _client
224
self._stripped_path = self._path.rsplit(",", 1)[0]
226
def external_url(self):
229
def has(self, relpath):
232
def _get_client(self):
233
raise NotImplementedError(self._get_client)
236
return self._stripped_path
69
238
def get(self, path):
70
239
raise NoSuchFile(path)
241
def abspath(self, relpath):
242
return urlutils.join(self.base, relpath)
72
244
def clone(self, offset=None):
73
245
"""See Transport.clone()."""
74
246
if offset is None:
77
249
newurl = urlutils.join(self.base, offset)
79
return GitSmartTransport(newurl, self._client)
251
return self.__class__(newurl, self._client)
254
class TCPGitSmartTransport(GitSmartTransport):
258
def _get_client(self):
259
if self._client is not None:
264
# return dulwich.client.LocalGitClient()
265
return dulwich.client.SubprocessGitClient()
266
return dulwich.client.TCPGitClient(
267
self._host, self._port, report_activity=self._report_activity)
270
class SSHSocketWrapper(object):
272
def __init__(self, sock):
275
def read(self, len=None):
276
return self.sock.recv(len)
278
def write(self, data):
279
return self.sock.write(data)
282
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
285
class DulwichSSHVendor(dulwich.client.SSHVendor):
288
from ..transport import ssh
289
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
291
def run_command(self, host, command, username=None, port=None):
292
connection = self.bzr_ssh_vendor.connect_ssh(
293
username=username, password=None, port=port, host=host,
295
(kind, io_object) = connection.get_sock_or_pipes()
297
return SSHSocketWrapper(io_object)
299
raise AssertionError("Unknown io object kind %r'" % kind)
302
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
305
class SSHGitSmartTransport(GitSmartTransport):
310
path = self._stripped_path
311
if path.startswith("/~/"):
315
def _get_client(self):
316
if self._client is not None:
320
location_config = config.LocationConfig(self.base)
321
client = dulwich.client.SSHGitClient(
322
self._host, self._port, self._username,
323
report_activity=self._report_activity)
324
# Set up alternate pack program paths
325
upload_pack = location_config.get_user_option('git_upload_pack')
327
client.alternative_paths["upload-pack"] = upload_pack
328
receive_pack = location_config.get_user_option('git_receive_pack')
330
client.alternative_paths["receive-pack"] = receive_pack
334
class RemoteGitBranchFormat(GitBranchFormat):
336
def get_format_description(self):
337
return 'Remote Git Branch'
340
def _matchingcontroldir(self):
341
return RemoteGitControlDirFormat()
343
def initialize(self, a_controldir, name=None, repository=None,
344
append_revisions_only=None):
345
raise UninitializableFormat(self)
348
class DefaultProgressReporter(object):
350
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
351
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
353
def __init__(self, pb):
356
def progress(self, text):
357
text = text.rstrip(b"\r\n")
358
text = text.decode('utf-8')
359
if text.lower().startswith('error: '):
360
trace.show_error('git: %s', text[len(b'error: '):])
362
trace.mutter("git: %s", text)
363
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
365
(text, pct, current, total) = g.groups()
366
self.pb.update(text, int(current), int(total))
368
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
370
(text, total) = g.groups()
371
self.pb.update(text, None, int(total))
373
trace.note("%s", text)
82
376
class RemoteGitDir(GitDir):
84
def __init__(self, transport, lockfiles, format):
378
def __init__(self, transport, format, client, client_path):
85
379
self._format = format
86
380
self.root_transport = transport
87
381
self.transport = transport
88
self._lockfiles = lockfiles
382
self._mode_check_done = None
383
self._client = client
384
self._client_path = client_path
385
self.base = self.root_transport.base
389
def _gitrepository_class(self):
390
return RemoteGitRepository
392
def archive(self, format, committish, write_data, progress=None,
393
write_error=None, subdirs=None, prefix=None):
395
pb = ui.ui_factory.nested_progress_bar()
396
progress = DefaultProgressReporter(pb).progress
399
def progress_wrapper(message):
400
if message.startswith(b"fatal: Unknown archive format \'"):
401
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
402
raise errors.NoSuchExportFormat(format.decode('ascii'))
403
return progress(message)
405
self._client.archive(
406
self._client_path, committish, write_data, progress_wrapper,
408
format=(format.encode('ascii') if format else None),
410
prefix=(prefix.encode('utf-8') if prefix else None))
411
except GitProtocolError as e:
412
raise parse_git_error(self.transport.external_url(), e)
417
def fetch_pack(self, determine_wants, graph_walker, pack_data,
420
pb = ui.ui_factory.nested_progress_bar()
421
progress = DefaultProgressReporter(pb).progress
425
result = self._client.fetch_pack(
426
self._client_path, determine_wants, graph_walker, pack_data,
428
if result.refs is None:
430
self._refs = remote_refs_dict_to_container(
431
result.refs, result.symrefs)
433
except GitProtocolError as e:
434
raise parse_git_error(self.transport.external_url(), e)
439
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
441
pb = ui.ui_factory.nested_progress_bar()
442
progress = DefaultProgressReporter(pb).progress
446
def get_changed_refs_wrapper(remote_refs):
447
if self._refs is not None:
448
update_refs_container(self._refs, remote_refs)
449
return get_changed_refs(remote_refs)
451
return self._client.send_pack(
452
self._client_path, get_changed_refs_wrapper,
453
generate_pack_data, progress)
454
except GitProtocolError as e:
455
raise parse_git_error(self.transport.external_url(), e)
460
def create_branch(self, name=None, repository=None,
461
append_revisions_only=None, ref=None):
462
refname = self._get_selected_ref(name, ref)
463
if refname != b'HEAD' and refname in self.get_refs_container():
464
raise AlreadyBranchError(self.user_url)
465
ref_chain, unused_sha = self.get_refs_container().follow(
466
self._get_selected_ref(name))
467
if ref_chain and ref_chain[0] == b'HEAD':
468
refname = ref_chain[1]
469
repo = self.open_repository()
470
return RemoteGitBranch(self, repo, refname)
472
def destroy_branch(self, name=None):
473
refname = self._get_selected_ref(name)
475
def get_changed_refs(old_refs):
477
if refname not in old_refs:
478
raise NotBranchError(self.user_url)
479
ret[refname] = dulwich.client.ZERO_SHA
482
def generate_pack_data(have, want, ofs_delta=False):
483
return pack_objects_to_data([])
484
self.send_pack(get_changed_refs, generate_pack_data)
488
return self.control_url
491
def user_transport(self):
492
return self.root_transport
495
def control_url(self):
496
return self.control_transport.base
499
def control_transport(self):
500
return self.root_transport
90
502
def open_repository(self):
91
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
503
return RemoteGitRepository(self)
505
def get_branch_reference(self, name=None):
506
ref = branch_name_to_ref(name)
507
val = self.get_refs_container().read_ref(ref)
508
if val.startswith(SYMREF):
509
return val[len(SYMREF):]
512
def open_branch(self, name=None, unsupported=False,
513
ignore_fallbacks=False, ref=None, possible_transports=None,
94
515
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
516
ref = self._get_selected_ref(name, ref)
518
if not nascent_ok and ref not in self.get_refs_container():
519
raise NotBranchError(
520
self.root_transport.base, controldir=self)
521
except NotGitRepository:
522
raise NotBranchError(self.root_transport.base,
524
ref_chain, unused_sha = self.get_refs_container().follow(ref)
525
return RemoteGitBranch(self, repo, ref_chain[-1])
98
def open_workingtree(self):
527
def open_workingtree(self, recommend_upgrade=False):
99
528
raise NotLocalUrl(self.transport.base)
530
def has_workingtree(self):
533
def get_peeled(self, name):
534
return self.get_refs_container().get_peeled(name)
536
def get_refs_container(self):
537
if self._refs is not None:
539
result = self.fetch_pack(lambda x: None, None,
541
lambda x: trace.mutter("git: %s" % x))
542
self._refs = remote_refs_dict_to_container(
543
result.refs, result.symrefs)
546
def push_branch(self, source, revision_id=None, overwrite=False,
547
remember=False, create_prefix=False, lossy=False,
548
name=None, tag_selector=None):
549
"""Push the source branch into this ControlDir."""
550
if revision_id is None:
551
# No revision supplied by the user, default to the branch
553
revision_id = source.last_revision()
555
push_result = GitPushResult()
556
push_result.workingtree_updated = None
557
push_result.master_branch = None
558
push_result.source_branch = source
559
push_result.stacked_on = None
560
push_result.branch_push_result = None
561
repo = self.find_repository()
562
refname = self._get_selected_ref(name)
563
ref_chain, old_sha = self.get_refs_container().follow(refname)
565
actual_refname = ref_chain[-1]
567
actual_refname = refname
568
if isinstance(source, GitBranch) and lossy:
569
raise errors.LossyPushToSameVCS(source.controldir, self)
570
source_store = get_object_store(source.repository)
571
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
572
def get_changed_refs(remote_refs):
573
if self._refs is not None:
574
update_refs_container(self._refs, remote_refs)
576
# TODO(jelmer): Unpeel if necessary
577
push_result.new_original_revid = revision_id
579
new_sha = source_store._lookup_revision_sha1(revision_id)
582
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
583
except errors.NoSuchRevision:
584
raise errors.NoRoundtrippingSupport(
585
source, self.open_branch(name=name, nascent_ok=True))
587
if remote_divergence(old_sha, new_sha, source_store):
588
raise DivergedBranches(
589
source, self.open_branch(name, nascent_ok=True))
590
ret[actual_refname] = new_sha
592
for tagname, revid in source.tags.get_tag_dict().items():
593
if tag_selector and not tag_selector(tagname):
597
new_sha = source_store._lookup_revision_sha1(revid)
599
if source.repository.has_revision(revid):
603
new_sha = repo.lookup_bzr_revision_id(revid)[0]
604
except errors.NoSuchRevision:
606
ret[tag_name_to_ref(tagname)] = new_sha
608
with source_store.lock_read():
609
def generate_pack_data(have, want, progress=None,
611
git_repo = getattr(source.repository, '_git', None)
613
shallow = git_repo.get_shallow()
617
return source_store.generate_lossy_pack_data(
618
have, want, shallow=shallow,
619
progress=progress, ofs_delta=ofs_delta)
621
return source_store.generate_pack_data(
622
have, want, shallow=shallow,
623
progress=progress, ofs_delta=ofs_delta)
625
return source_store.generate_pack_data(
626
have, want, progress=progress, ofs_delta=ofs_delta)
627
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
628
push_result.new_revid = repo.lookup_foreign_revision_id(
629
new_refs[actual_refname])
630
if old_sha is not None:
631
push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
633
push_result.old_revid = NULL_REVISION
634
if self._refs is not None:
635
update_refs_container(self._refs, new_refs)
636
push_result.target_branch = self.open_branch(name)
637
if old_sha is not None:
638
push_result.branch_push_result = GitBranchPushResult()
639
push_result.branch_push_result.source_branch = source
640
push_result.branch_push_result.target_branch = (
641
push_result.target_branch)
642
push_result.branch_push_result.local_branch = None
643
push_result.branch_push_result.master_branch = (
644
push_result.target_branch)
645
push_result.branch_push_result.old_revid = push_result.old_revid
646
push_result.branch_push_result.new_revid = push_result.new_revid
647
push_result.branch_push_result.new_original_revid = (
648
push_result.new_original_revid)
649
if source.get_push_location() is None or remember:
650
source.set_push_location(push_result.target_branch.base)
653
def _find_commondir(self):
654
# There is no way to find the commondir, if there is any.
658
class EmptyObjectStoreIterator(dict):
660
def iterobjects(self):
664
class TemporaryPackIterator(Pack):
666
def __init__(self, path, resolve_ext_ref):
667
super(TemporaryPackIterator, self).__init__(
668
path, resolve_ext_ref=resolve_ext_ref)
669
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
671
def _idx_load_or_generate(self, path):
672
if not os.path.exists(path):
673
with ui.ui_factory.nested_progress_bar() as pb:
674
def report_progress(cur, total):
675
pb.update("generating index", cur, total)
676
self.data.create_index(path, progress=report_progress)
677
return load_pack_index(path)
680
if self._idx is not None:
682
os.remove(self._idx_path)
683
if self._data is not None:
685
os.remove(self._data_path)
688
class BzrGitHttpClient(dulwich.client.HttpGitClient):
690
def __init__(self, transport, *args, **kwargs):
691
self.transport = transport
692
url = urlutils.URL.from_string(transport.external_url())
693
url.user = url.quoted_user = None
694
url.password = url.quoted_password = None
695
url = urlutils.strip_segment_parameters(str(url))
696
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
698
def _http_request(self, url, headers=None, data=None,
699
allow_compression=False):
700
"""Perform HTTP request.
702
:param url: Request URL.
703
:param headers: Optional custom headers to override defaults.
704
:param data: Request data.
705
:param allow_compression: Allow GZipped communication.
706
:return: Tuple (`response`, `read`), where response is an `urllib3`
707
response object with additional `content_type` and
708
`redirect_location` properties, and `read` is a consumable read
709
method for the response data.
711
if is_github_url(url):
712
headers['User-agent'] = user_agent_for_github()
713
headers["Pragma"] = "no-cache"
714
if allow_compression:
715
headers["Accept-Encoding"] = "gzip"
717
headers["Accept-Encoding"] = "identity"
719
response = self.transport.request(
720
('GET' if data is None else 'POST'),
723
headers=headers, retries=8)
725
if response.status == 404:
726
raise NotGitRepository()
727
elif response.status != 200:
728
raise GitProtocolError("unexpected http resp %d for %s" %
729
(response.status, url))
731
# TODO: Optimization available by adding `preload_content=False` to the
732
# request and just passing the `read` method on instead of going via
733
# `BytesIO`, if we can guarantee that the entire response is consumed
734
# before issuing the next to still allow for connection reuse from the
736
if response.getheader("Content-Encoding") == "gzip":
737
read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
741
class WrapResponse(object):
743
def __init__(self, response):
744
self._response = response
745
self.status = response.status
746
self.content_type = response.getheader("Content-Type")
747
self.redirect_location = response._actual.geturl()
750
return self._response.readlines()
755
return WrapResponse(response), read
758
def _git_url_and_path_from_transport(external_url):
759
url = urlutils.strip_segment_parameters(external_url)
760
return urlparse.urlsplit(url)
763
class RemoteGitControlDirFormat(GitControlDirFormat):
764
"""The .git directory control format."""
766
supports_workingtrees = False
769
def _known_formats(self):
770
return set([RemoteGitControlDirFormat()])
772
def get_branch_format(self):
773
return RemoteGitBranchFormat()
776
def repository_format(self):
777
return GitRepositoryFormat()
779
def is_initializable(self):
782
def is_supported(self):
785
def open(self, transport, _found=None):
786
"""Open this directory.
789
split_url = _git_url_and_path_from_transport(transport.external_url())
790
if isinstance(transport, GitSmartTransport):
791
client = transport._get_client()
792
elif split_url.scheme in ("http", "https"):
793
client = BzrGitHttpClient(transport)
794
elif split_url.scheme in ('file', ):
795
client = dulwich.client.LocalGitClient()
797
raise NotBranchError(transport.base)
799
pass # TODO(jelmer): Actually probe for something
800
return RemoteGitDir(transport, self, client, split_url.path)
802
def get_format_description(self):
803
return "Remote Git Repository"
805
def initialize_on_transport(self, transport):
806
raise UninitializableFormat(self)
808
def supports_transport(self, transport):
810
external_url = transport.external_url()
811
except InProcessTransport:
812
raise NotBranchError(path=transport.base)
813
return (external_url.startswith("http:")
814
or external_url.startswith("https:")
815
or external_url.startswith("git+")
816
or external_url.startswith("git:"))
819
class GitRemoteRevisionTree(RevisionTree):
821
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
822
"""Create an archive of this tree.
824
:param format: Format name (e.g. 'tar')
825
:param name: target file name
826
:param root: Root directory name (or None)
827
:param subdir: Subdirectory to export (or None)
828
:return: Iterator over archive chunks
830
commit = self._repository.lookup_bzr_revision_id(
831
self.get_revision_id())[0]
832
f = tempfile.SpooledTemporaryFile()
833
# git-upload-archive(1) generaly only supports refs. So let's see if we
837
self._repository.controldir.get_refs_container().as_dict().items()}
839
committish = reverse_refs[commit]
841
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
842
# Let's hope for the best.
844
self._repository.archive(
845
format, committish, f.write,
846
subdirs=([subdir] if subdir else None),
847
prefix=(root + '/') if root else '')
849
return osutils.file_iterator(f)
851
def is_versioned(self, path):
852
raise GitSmartRemoteNotSupported(self.is_versioned, self)
854
def has_filename(self, path):
855
raise GitSmartRemoteNotSupported(self.has_filename, self)
857
def get_file_text(self, path):
858
raise GitSmartRemoteNotSupported(self.get_file_text, self)
860
def list_files(self, include_root=False, from_dir=None, recursive=True):
861
raise GitSmartRemoteNotSupported(self.list_files, self)
102
864
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,
866
supports_random_access = False
870
return self.control_url
872
def get_parent_map(self, revids):
873
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
875
def archive(self, *args, **kwargs):
876
return self.controldir.archive(*args, **kwargs)
878
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
880
return self.controldir.fetch_pack(
881
determine_wants, graph_walker, pack_data, progress)
883
def send_pack(self, get_changed_refs, generate_pack_data):
884
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
886
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
888
fd, path = tempfile.mkstemp(suffix=".pack")
890
self.fetch_pack(determine_wants, graph_walker,
891
lambda x: os.write(fd, x), progress)
894
if os.path.getsize(path) == 0:
895
return EmptyObjectStoreIterator()
896
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
898
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
899
# This won't work for any round-tripped bzr revisions, but it's a
902
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
903
except InvalidRevisionId:
904
raise NoSuchRevision(self, bzr_revid)
906
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
907
"""Lookup a revision id.
911
mapping = self.get_mapping()
912
# Not really an easy way to parse foreign revids here..
913
return mapping.revision_id_foreign_to_bzr(foreign_revid)
915
def revision_tree(self, revid):
916
return GitRemoteRevisionTree(self, revid)
918
def get_revisions(self, revids):
919
raise GitSmartRemoteNotSupported(self.get_revisions, self)
921
def has_revisions(self, revids):
922
raise GitSmartRemoteNotSupported(self.get_revisions, self)
925
class RemoteGitTagDict(GitTags):
927
def set_tag(self, name, revid):
928
sha = self.branch.lookup_bzr_revision_id(revid)[0]
929
self._set_ref(name, sha)
931
def delete_tag(self, name):
932
self._set_ref(name, dulwich.client.ZERO_SHA)
934
def _set_ref(self, name, sha):
935
ref = tag_name_to_ref(name)
937
def get_changed_refs(old_refs):
939
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
940
raise NoSuchTag(name)
944
def generate_pack_data(have, want, ofs_delta=False):
945
return pack_objects_to_data([])
946
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
949
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)
951
def __init__(self, controldir, repository, name):
953
super(RemoteGitBranch, self).__init__(controldir, repository, name,
954
RemoteGitBranchFormat())
956
def last_revision_info(self):
957
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
961
return self.control_url
964
def control_url(self):
967
def revision_id_to_revno(self, revision_id):
968
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
970
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
971
return self.lookup_foreign_revision_id(self.head)
975
if self._sha is not None:
977
refs = self.controldir.get_refs_container()
978
name = branch_name_to_ref(self.name)
980
self._sha = refs[name]
982
raise NoSuchRef(name, self.repository.user_url, refs)
985
def _synchronize_history(self, destination, revision_id):
986
"""See Branch._synchronize_history()."""
987
if revision_id is None:
988
revision_id = self.last_revision()
989
destination.generate_revision_history(revision_id)
991
def _get_parent_location(self):
994
def get_push_location(self):
997
def set_push_location(self, url):
1000
def _iter_tag_refs(self):
1001
"""Iterate over the tag refs.
1003
:param refs: Refs dictionary (name -> git sha1)
1004
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
1006
refs = self.controldir.get_refs_container()
1007
for ref_name, unpeeled in refs.as_dict().items():
1009
tag_name = ref_to_tag_name(ref_name)
1010
except (ValueError, UnicodeDecodeError):
1012
peeled = refs.get_peeled(ref_name)
1014
# Let's just hope it's a commit
1016
if not isinstance(tag_name, str):
1017
raise TypeError(tag_name)
1018
yield (ref_name, tag_name, peeled, unpeeled)
1020
def set_last_revision_info(self, revno, revid):
1021
self.generate_revision_history(revid)
1023
def generate_revision_history(self, revision_id, last_rev=None,
1025
sha = self.lookup_bzr_revision_id(revision_id)[0]
1026
def get_changed_refs(old_refs):
1027
return {self.ref: sha}
1028
def generate_pack_data(have, want, ofs_delta=False):
1029
return pack_objects_to_data([])
1030
self.repository.send_pack(get_changed_refs, generate_pack_data)
1034
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1037
for k, v in refs_dict.items():
1042
for name, target in symrefs_dict.items():
1043
base[name] = SYMREF + target
1044
ret = DictRefsContainer(base)
1045
ret._peeled = peeled
1049
def update_refs_container(container, refs_dict):
1052
for k, v in refs_dict.items():
1057
container._peeled = peeled
1058
container._refs.update(base)