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():
610
generate_pack_data = source_store.generate_lossy_pack_data
612
generate_pack_data = source_store.generate_pack_data
613
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
614
push_result.new_revid = repo.lookup_foreign_revision_id(
615
new_refs[actual_refname])
616
if old_sha is not None:
617
push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
619
push_result.old_revid = NULL_REVISION
620
if self._refs is not None:
621
update_refs_container(self._refs, new_refs)
622
push_result.target_branch = self.open_branch(name)
623
if old_sha is not None:
624
push_result.branch_push_result = GitBranchPushResult()
625
push_result.branch_push_result.source_branch = source
626
push_result.branch_push_result.target_branch = (
627
push_result.target_branch)
628
push_result.branch_push_result.local_branch = None
629
push_result.branch_push_result.master_branch = (
630
push_result.target_branch)
631
push_result.branch_push_result.old_revid = push_result.old_revid
632
push_result.branch_push_result.new_revid = push_result.new_revid
633
push_result.branch_push_result.new_original_revid = (
634
push_result.new_original_revid)
635
if source.get_push_location() is None or remember:
636
source.set_push_location(push_result.target_branch.base)
639
def _find_commondir(self):
640
# There is no way to find the commondir, if there is any.
644
class EmptyObjectStoreIterator(dict):
646
def iterobjects(self):
650
class TemporaryPackIterator(Pack):
652
def __init__(self, path, resolve_ext_ref):
653
super(TemporaryPackIterator, self).__init__(
654
path, resolve_ext_ref=resolve_ext_ref)
655
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
657
def _idx_load_or_generate(self, path):
658
if not os.path.exists(path):
659
with ui.ui_factory.nested_progress_bar() as pb:
660
def report_progress(cur, total):
661
pb.update("generating index", cur, total)
662
self.data.create_index(path, progress=report_progress)
663
return load_pack_index(path)
666
if self._idx is not None:
668
os.remove(self._idx_path)
669
if self._data is not None:
671
os.remove(self._data_path)
674
class BzrGitHttpClient(dulwich.client.HttpGitClient):
676
def __init__(self, transport, *args, **kwargs):
677
self.transport = transport
678
url = urlutils.URL.from_string(transport.external_url())
679
url.user = url.quoted_user = None
680
url.password = url.quoted_password = None
681
url = urlutils.strip_segment_parameters(str(url))
682
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
684
def _http_request(self, url, headers=None, data=None,
685
allow_compression=False):
686
"""Perform HTTP request.
688
:param url: Request URL.
689
:param headers: Optional custom headers to override defaults.
690
:param data: Request data.
691
:param allow_compression: Allow GZipped communication.
692
:return: Tuple (`response`, `read`), where response is an `urllib3`
693
response object with additional `content_type` and
694
`redirect_location` properties, and `read` is a consumable read
695
method for the response data.
697
if is_github_url(url):
698
headers['User-agent'] = user_agent_for_github()
699
headers["Pragma"] = "no-cache"
700
if allow_compression:
701
headers["Accept-Encoding"] = "gzip"
703
headers["Accept-Encoding"] = "identity"
705
response = self.transport.request(
706
('GET' if data is None else 'POST'),
709
headers=headers, retries=8)
711
if response.status == 404:
712
raise NotGitRepository()
713
elif response.status != 200:
714
raise GitProtocolError("unexpected http resp %d for %s" %
715
(response.code, url))
717
# TODO: Optimization available by adding `preload_content=False` to the
718
# request and just passing the `read` method on instead of going via
719
# `BytesIO`, if we can guarantee that the entire response is consumed
720
# before issuing the next to still allow for connection reuse from the
722
if response.getheader("Content-Encoding") == "gzip":
723
read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
727
class WrapResponse(object):
729
def __init__(self, response):
730
self._response = response
731
self.status = response.status
732
self.content_type = response.getheader("Content-Type")
733
self.redirect_location = response._actual.geturl()
736
return self._response.readlines()
741
return WrapResponse(response), read
744
def _git_url_and_path_from_transport(external_url):
745
url = urlutils.strip_segment_parameters(external_url)
746
return urlparse.urlsplit(url)
749
class RemoteGitControlDirFormat(GitControlDirFormat):
750
"""The .git directory control format."""
752
supports_workingtrees = False
755
def _known_formats(self):
756
return set([RemoteGitControlDirFormat()])
758
def get_branch_format(self):
759
return RemoteGitBranchFormat()
762
def repository_format(self):
763
return GitRepositoryFormat()
765
def is_initializable(self):
768
def is_supported(self):
771
def open(self, transport, _found=None):
772
"""Open this directory.
775
split_url = _git_url_and_path_from_transport(transport.external_url())
776
if isinstance(transport, GitSmartTransport):
777
client = transport._get_client()
778
elif split_url.scheme in ("http", "https"):
779
client = BzrGitHttpClient(transport)
780
elif split_url.scheme in ('file', ):
781
client = dulwich.client.LocalGitClient()
783
raise NotBranchError(transport.base)
785
pass # TODO(jelmer): Actually probe for something
786
return RemoteGitDir(transport, self, client, split_url.path)
788
def get_format_description(self):
789
return "Remote Git Repository"
791
def initialize_on_transport(self, transport):
792
raise UninitializableFormat(self)
794
def supports_transport(self, transport):
796
external_url = transport.external_url()
797
except InProcessTransport:
798
raise NotBranchError(path=transport.base)
799
return (external_url.startswith("http:")
800
or external_url.startswith("https:")
801
or external_url.startswith("git+")
802
or external_url.startswith("git:"))
805
class GitRemoteRevisionTree(RevisionTree):
807
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
808
"""Create an archive of this tree.
810
:param format: Format name (e.g. 'tar')
811
:param name: target file name
812
:param root: Root directory name (or None)
813
:param subdir: Subdirectory to export (or None)
814
:return: Iterator over archive chunks
816
commit = self._repository.lookup_bzr_revision_id(
817
self.get_revision_id())[0]
818
f = tempfile.SpooledTemporaryFile()
819
# git-upload-archive(1) generaly only supports refs. So let's see if we
823
self._repository.controldir.get_refs_container().as_dict().items()}
825
committish = reverse_refs[commit]
827
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
828
# Let's hope for the best.
830
self._repository.archive(
831
format, committish, f.write,
832
subdirs=([subdir] if subdir else None),
833
prefix=(root + '/') if root else '')
835
return osutils.file_iterator(f)
837
def is_versioned(self, path):
838
raise GitSmartRemoteNotSupported(self.is_versioned, self)
840
def has_filename(self, path):
841
raise GitSmartRemoteNotSupported(self.has_filename, self)
843
def get_file_text(self, path):
844
raise GitSmartRemoteNotSupported(self.get_file_text, self)
846
def list_files(self, include_root=False, from_dir=None, recursive=True):
847
raise GitSmartRemoteNotSupported(self.list_files, self)
102
850
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,
852
supports_random_access = False
856
return self.control_url
858
def get_parent_map(self, revids):
859
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
861
def archive(self, *args, **kwargs):
862
return self.controldir.archive(*args, **kwargs)
864
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
866
return self.controldir.fetch_pack(
867
determine_wants, graph_walker, pack_data, progress)
869
def send_pack(self, get_changed_refs, generate_pack_data):
870
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
872
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
874
fd, path = tempfile.mkstemp(suffix=".pack")
876
self.fetch_pack(determine_wants, graph_walker,
877
lambda x: os.write(fd, x), progress)
880
if os.path.getsize(path) == 0:
881
return EmptyObjectStoreIterator()
882
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
884
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
885
# This won't work for any round-tripped bzr revisions, but it's a
888
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
889
except InvalidRevisionId:
890
raise NoSuchRevision(self, bzr_revid)
892
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
893
"""Lookup a revision id.
897
mapping = self.get_mapping()
898
# Not really an easy way to parse foreign revids here..
899
return mapping.revision_id_foreign_to_bzr(foreign_revid)
901
def revision_tree(self, revid):
902
return GitRemoteRevisionTree(self, revid)
904
def get_revisions(self, revids):
905
raise GitSmartRemoteNotSupported(self.get_revisions, self)
907
def has_revisions(self, revids):
908
raise GitSmartRemoteNotSupported(self.get_revisions, self)
911
class RemoteGitTagDict(GitTags):
913
def set_tag(self, name, revid):
914
sha = self.branch.lookup_bzr_revision_id(revid)[0]
915
self._set_ref(name, sha)
917
def delete_tag(self, name):
918
self._set_ref(name, dulwich.client.ZERO_SHA)
920
def _set_ref(self, name, sha):
921
ref = tag_name_to_ref(name)
923
def get_changed_refs(old_refs):
925
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
926
raise NoSuchTag(name)
930
def generate_pack_data(have, want, ofs_delta=False):
931
return pack_objects_to_data([])
932
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
935
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)
937
def __init__(self, controldir, repository, name):
939
super(RemoteGitBranch, self).__init__(controldir, repository, name,
940
RemoteGitBranchFormat())
942
def last_revision_info(self):
943
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
947
return self.control_url
950
def control_url(self):
953
def revision_id_to_revno(self, revision_id):
954
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
956
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
957
return self.lookup_foreign_revision_id(self.head)
961
if self._sha is not None:
963
refs = self.controldir.get_refs_container()
964
name = branch_name_to_ref(self.name)
966
self._sha = refs[name]
968
raise NoSuchRef(name, self.repository.user_url, refs)
971
def _synchronize_history(self, destination, revision_id):
972
"""See Branch._synchronize_history()."""
973
if revision_id is None:
974
revision_id = self.last_revision()
975
destination.generate_revision_history(revision_id)
977
def _get_parent_location(self):
980
def get_push_location(self):
983
def set_push_location(self, url):
986
def _iter_tag_refs(self):
987
"""Iterate over the tag refs.
989
:param refs: Refs dictionary (name -> git sha1)
990
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
992
refs = self.controldir.get_refs_container()
993
for ref_name, unpeeled in refs.as_dict().items():
995
tag_name = ref_to_tag_name(ref_name)
996
except (ValueError, UnicodeDecodeError):
998
peeled = refs.get_peeled(ref_name)
1000
# Let's just hope it's a commit
1002
if not isinstance(tag_name, str):
1003
raise TypeError(tag_name)
1004
yield (ref_name, tag_name, peeled, unpeeled)
1006
def set_last_revision_info(self, revno, revid):
1007
self.generate_revision_history(revid)
1009
def generate_revision_history(self, revision_id, last_rev=None,
1011
sha = self.lookup_bzr_revision_id(revision_id)[0]
1012
def get_changed_refs(old_refs):
1013
return {self.ref: sha}
1014
def generate_pack_data(have, want, ofs_delta=False):
1015
return pack_objects_to_data([])
1016
self.repository.send_pack(get_changed_refs, generate_pack_data)
1020
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1023
for k, v in refs_dict.items():
1028
for name, target in symrefs_dict.items():
1029
base[name] = SYMREF + target
1030
ret = DictRefsContainer(base)
1031
ret._peeled = peeled
1035
def update_refs_container(container, refs_dict):
1038
for k, v in refs_dict.items():
1043
container._peeled = peeled
1044
container._refs.update(base)