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."""
19
from __future__ import absolute_import
36
from ..errors import (
48
UninitializableFormat,
50
from ..revisiontree import RevisionTree
51
from ..sixish import text_type
52
from ..transport import (
54
register_urlparse_netloc_protocol,
59
user_agent_for_github,
75
GitSmartRemoteNotSupported,
78
from .mapping import (
81
from .object_store import (
87
from .repository import (
99
from dulwich.errors import (
103
from dulwich.pack import (
105
pack_objects_to_data,
107
from dulwich.protocol import ZERO_SHA
108
from dulwich.refs import (
112
from dulwich.repo import (
120
import urllib.parse as urlparse
121
from urllib.parse import splituser, splitnport
124
from urllib import splituser, splitnport
126
# urlparse only supports a limited number of schemes by default
127
register_urlparse_netloc_protocol('git')
128
register_urlparse_netloc_protocol('git+ssh')
130
from dulwich.pack import load_pack_index
133
class GitPushResult(PushResult):
135
def _lookup_revno(self, revid):
137
return _quick_lookup_revno(self.source_branch, self.target_branch,
139
except GitSmartRemoteNotSupported:
144
return self._lookup_revno(self.old_revid)
148
return self._lookup_revno(self.new_revid)
151
# Don't run any tests on GitSmartTransport as it is not intended to be
152
# a full implementation of Transport
153
def get_test_permutations():
157
def split_git_url(url):
161
:return: Tuple with host, port, username, path.
163
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
164
path = urlparse.unquote(loc)
165
if path.startswith("/~"):
167
(username, hostport) = splituser(netloc)
168
(host, port) = splitnport(hostport, None)
169
return (host, port, username, path)
172
class RemoteGitError(BzrError):
174
_fmt = "Remote server error: %(msg)s"
177
class HeadUpdateFailed(BzrError):
179
_fmt = ("Unable to update remote HEAD branch. To update the master "
180
"branch, specify the URL %(base_url)s,branch=master.")
182
def __init__(self, base_url):
183
super(HeadUpdateFailed, self).__init__()
184
self.base_url = base_url
187
def parse_git_error(url, message):
188
"""Parse a remote git server error and return a bzr exception.
190
:param url: URL of the remote repository
191
:param message: Message sent by the remote git server
193
message = str(message).strip()
194
if (message.startswith("Could not find Repository ")
195
or message == 'Repository not found.'
196
or (message.startswith('Repository ') and
197
message.endswith(' not found.'))):
198
return NotBranchError(url, message)
199
if message == "HEAD failed to update":
200
base_url, _ = urlutils.split_segment_parameters(url)
201
return HeadUpdateFailed(base_url)
202
if message.startswith('access denied or repository not exported:'):
203
extra, path = message.split(': ', 1)
204
return PermissionDenied(path, extra)
205
if message.endswith('You are not allowed to push code to this project.'):
206
return PermissionDenied(url, message)
207
if message.endswith(' does not appear to be a git repository'):
208
return NotBranchError(url, message)
209
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
211
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
212
# Don't know, just return it to the user as-is
213
return RemoteGitError(message)
38
216
class GitSmartTransport(Transport):
40
218
def __init__(self, url, _client=None):
41
219
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():
220
(self._host, self._port, self._username, self._path) = \
222
if 'transport' in debug.debug_flags:
223
trace.mutter('host: %r, user: %r, port: %r, path: %r',
224
self._host, self._username, self._port, self._path)
225
self._client = _client
226
self._stripped_path = self._path.rsplit(",", 1)[0]
228
def external_url(self):
231
def has(self, relpath):
234
def _get_client(self):
235
raise NotImplementedError(self._get_client)
238
return self._stripped_path
69
240
def get(self, path):
70
241
raise NoSuchFile(path)
243
def abspath(self, relpath):
244
return urlutils.join(self.base, relpath)
72
246
def clone(self, offset=None):
73
247
"""See Transport.clone()."""
74
248
if offset is None:
77
251
newurl = urlutils.join(self.base, offset)
79
return GitSmartTransport(newurl, self._client)
253
return self.__class__(newurl, self._client)
256
class TCPGitSmartTransport(GitSmartTransport):
260
def _get_client(self):
261
if self._client is not None:
266
# return dulwich.client.LocalGitClient()
267
return dulwich.client.SubprocessGitClient()
268
return dulwich.client.TCPGitClient(
269
self._host, self._port, report_activity=self._report_activity)
272
class SSHSocketWrapper(object):
274
def __init__(self, sock):
277
def read(self, len=None):
278
return self.sock.recv(len)
280
def write(self, data):
281
return self.sock.write(data)
284
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
287
class DulwichSSHVendor(dulwich.client.SSHVendor):
290
from ..transport import ssh
291
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
293
def run_command(self, host, command, username=None, port=None):
294
connection = self.bzr_ssh_vendor.connect_ssh(
295
username=username, password=None, port=port, host=host,
297
(kind, io_object) = connection.get_sock_or_pipes()
299
return SSHSocketWrapper(io_object)
301
raise AssertionError("Unknown io object kind %r'" % kind)
304
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
307
class SSHGitSmartTransport(GitSmartTransport):
312
path = self._stripped_path
313
if path.startswith("/~/"):
317
def _get_client(self):
318
if self._client is not None:
322
location_config = config.LocationConfig(self.base)
323
client = dulwich.client.SSHGitClient(
324
self._host, self._port, self._username,
325
report_activity=self._report_activity)
326
# Set up alternate pack program paths
327
upload_pack = location_config.get_user_option('git_upload_pack')
329
client.alternative_paths["upload-pack"] = upload_pack
330
receive_pack = location_config.get_user_option('git_receive_pack')
332
client.alternative_paths["receive-pack"] = receive_pack
336
class RemoteGitBranchFormat(GitBranchFormat):
338
def get_format_description(self):
339
return 'Remote Git Branch'
342
def _matchingcontroldir(self):
343
return RemoteGitControlDirFormat()
345
def initialize(self, a_controldir, name=None, repository=None,
346
append_revisions_only=None):
347
raise UninitializableFormat(self)
350
class DefaultProgressReporter(object):
352
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
353
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
355
def __init__(self, pb):
358
def progress(self, text):
359
text = text.rstrip(b"\r\n")
360
text = text.decode('utf-8')
361
if text.lower().startswith('error: '):
362
trace.show_error('git: %s', text[len(b'error: '):])
364
trace.mutter("git: %s", text)
365
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
367
(text, pct, current, total) = g.groups()
368
self.pb.update(text, int(current), int(total))
370
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
372
(text, total) = g.groups()
373
self.pb.update(text, None, int(total))
375
trace.note("%s", text)
82
378
class RemoteGitDir(GitDir):
84
def __init__(self, transport, lockfiles, format):
380
def __init__(self, transport, format, client, client_path):
85
381
self._format = format
86
382
self.root_transport = transport
87
383
self.transport = transport
88
self._lockfiles = lockfiles
384
self._mode_check_done = None
385
self._client = client
386
self._client_path = client_path
387
self.base = self.root_transport.base
391
def _gitrepository_class(self):
392
return RemoteGitRepository
394
def archive(self, format, committish, write_data, progress=None,
395
write_error=None, subdirs=None, prefix=None):
397
pb = ui.ui_factory.nested_progress_bar()
398
progress = DefaultProgressReporter(pb).progress
401
def progress_wrapper(message):
402
if message.startswith(b"fatal: Unknown archive format \'"):
403
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
404
raise errors.NoSuchExportFormat(format.decode('ascii'))
405
return progress(message)
407
self._client.archive(
408
self._client_path, committish, write_data, progress_wrapper,
410
format=(format.encode('ascii') if format else None),
412
prefix=(prefix.encode('utf-8') if prefix else None))
413
except GitProtocolError as e:
414
raise parse_git_error(self.transport.external_url(), e)
419
def fetch_pack(self, determine_wants, graph_walker, pack_data,
422
pb = ui.ui_factory.nested_progress_bar()
423
progress = DefaultProgressReporter(pb).progress
427
result = self._client.fetch_pack(
428
self._client_path, determine_wants, graph_walker, pack_data,
430
if result.refs is None:
432
self._refs = remote_refs_dict_to_container(
433
result.refs, result.symrefs)
435
except GitProtocolError as e:
436
raise parse_git_error(self.transport.external_url(), e)
441
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
443
pb = ui.ui_factory.nested_progress_bar()
444
progress = DefaultProgressReporter(pb).progress
448
def get_changed_refs_wrapper(refs):
449
# TODO(jelmer): This drops symref information
450
self._refs = remote_refs_dict_to_container(refs)
451
return get_changed_refs(refs)
453
return self._client.send_pack(
454
self._client_path, get_changed_refs_wrapper,
455
generate_pack_data, progress)
456
except GitProtocolError as e:
457
raise parse_git_error(self.transport.external_url(), e)
462
def create_branch(self, name=None, repository=None,
463
append_revisions_only=None, ref=None):
464
refname = self._get_selected_ref(name, ref)
465
if refname != b'HEAD' and refname in self.get_refs_container():
466
raise AlreadyBranchError(self.user_url)
467
if refname in self.get_refs_container():
468
ref_chain, unused_sha = self.get_refs_container().follow(
469
self._get_selected_ref(None))
470
if ref_chain[0] == b'HEAD':
471
refname = ref_chain[1]
472
repo = self.open_repository()
473
return RemoteGitBranch(self, repo, refname)
475
def destroy_branch(self, name=None):
476
refname = self._get_selected_ref(name)
478
def get_changed_refs(old_refs):
480
if refname not in ret:
481
raise NotBranchError(self.user_url)
482
ret[refname] = dulwich.client.ZERO_SHA
485
def generate_pack_data(have, want, ofs_delta=False):
486
return pack_objects_to_data([])
487
self.send_pack(get_changed_refs, generate_pack_data)
491
return self.control_url
494
def user_transport(self):
495
return self.root_transport
498
def control_url(self):
499
return self.control_transport.base
502
def control_transport(self):
503
return self.root_transport
90
505
def open_repository(self):
91
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
506
return RemoteGitRepository(self)
508
def get_branch_reference(self, name=None):
509
ref = branch_name_to_ref(name)
510
val = self.get_refs_container().read_ref(ref)
511
if val.startswith(SYMREF):
512
return val[len(SYMREF):]
515
def open_branch(self, name=None, unsupported=False,
516
ignore_fallbacks=False, ref=None, possible_transports=None,
94
518
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
519
ref = self._get_selected_ref(name, ref)
521
if not nascent_ok and ref not in self.get_refs_container():
522
raise NotBranchError(
523
self.root_transport.base, controldir=self)
524
except NotGitRepository:
525
raise NotBranchError(self.root_transport.base,
527
ref_chain, unused_sha = self.get_refs_container().follow(ref)
528
return RemoteGitBranch(self, repo, ref_chain[-1])
98
def open_workingtree(self):
530
def open_workingtree(self, recommend_upgrade=False):
99
531
raise NotLocalUrl(self.transport.base)
533
def has_workingtree(self):
536
def get_peeled(self, name):
537
return self.get_refs_container().get_peeled(name)
539
def get_refs_container(self):
540
if self._refs is not None:
542
result = self.fetch_pack(lambda x: None, None,
544
lambda x: trace.mutter("git: %s" % x))
545
self._refs = remote_refs_dict_to_container(
546
result.refs, result.symrefs)
549
def push_branch(self, source, revision_id=None, overwrite=False,
550
remember=False, create_prefix=False, lossy=False,
552
"""Push the source branch into this ControlDir."""
553
if revision_id is None:
554
# No revision supplied by the user, default to the branch
556
revision_id = source.last_revision()
558
push_result = GitPushResult()
559
push_result.workingtree_updated = None
560
push_result.master_branch = None
561
push_result.source_branch = source
562
push_result.stacked_on = None
563
push_result.branch_push_result = None
564
repo = self.find_repository()
565
refname = self._get_selected_ref(name)
566
if isinstance(source, GitBranch) and lossy:
567
raise errors.LossyPushToSameVCS(source.controldir, self)
568
source_store = get_object_store(source.repository)
569
with source_store.lock_read():
570
def get_changed_refs(refs):
571
self._refs = remote_refs_dict_to_container(refs)
573
# TODO(jelmer): Unpeel if necessary
574
push_result.new_original_revid = revision_id
576
new_sha = source_store._lookup_revision_sha1(revision_id)
579
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
580
except errors.NoSuchRevision:
581
raise errors.NoRoundtrippingSupport(
582
source, self.open_branch(name=name, nascent_ok=True))
584
if remote_divergence(ret.get(refname), new_sha,
586
raise DivergedBranches(
587
source, self.open_branch(name, nascent_ok=True))
588
ret[refname] = new_sha
591
generate_pack_data = source_store.generate_lossy_pack_data
593
generate_pack_data = source_store.generate_pack_data
594
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
595
push_result.new_revid = repo.lookup_foreign_revision_id(
598
old_remote = self._refs[refname]
600
old_remote = ZERO_SHA
601
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
602
self._refs = remote_refs_dict_to_container(new_refs)
603
push_result.target_branch = self.open_branch(name)
604
if old_remote != ZERO_SHA:
605
push_result.branch_push_result = GitBranchPushResult()
606
push_result.branch_push_result.source_branch = source
607
push_result.branch_push_result.target_branch = (
608
push_result.target_branch)
609
push_result.branch_push_result.local_branch = None
610
push_result.branch_push_result.master_branch = (
611
push_result.target_branch)
612
push_result.branch_push_result.old_revid = push_result.old_revid
613
push_result.branch_push_result.new_revid = push_result.new_revid
614
push_result.branch_push_result.new_original_revid = (
615
push_result.new_original_revid)
616
if source.get_push_location() is None or remember:
617
source.set_push_location(push_result.target_branch.base)
620
def _find_commondir(self):
621
# There is no way to find the commondir, if there is any.
625
class EmptyObjectStoreIterator(dict):
627
def iterobjects(self):
631
class TemporaryPackIterator(Pack):
633
def __init__(self, path, resolve_ext_ref):
634
super(TemporaryPackIterator, self).__init__(
635
path, resolve_ext_ref=resolve_ext_ref)
636
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
638
def _idx_load_or_generate(self, path):
639
if not os.path.exists(path):
640
pb = ui.ui_factory.nested_progress_bar()
642
def report_progress(cur, total):
643
pb.update("generating index", cur, total)
644
self.data.create_index(path,
645
progress=report_progress)
648
return load_pack_index(path)
651
if self._idx is not None:
653
os.remove(self._idx_path)
654
if self._data is not None:
656
os.remove(self._data_path)
659
class BzrGitHttpClient(dulwich.client.HttpGitClient):
661
def __init__(self, transport, *args, **kwargs):
662
self.transport = transport
663
super(BzrGitHttpClient, self).__init__(
664
transport.external_url(), *args, **kwargs)
666
def _http_request(self, url, headers=None, data=None,
667
allow_compression=False):
668
"""Perform HTTP request.
670
:param url: Request URL.
671
:param headers: Optional custom headers to override defaults.
672
:param data: Request data.
673
:param allow_compression: Allow GZipped communication.
674
:return: Tuple (`response`, `read`), where response is an `urllib3`
675
response object with additional `content_type` and
676
`redirect_location` properties, and `read` is a consumable read
677
method for the response data.
679
from breezy.transport.http._urllib2_wrappers import Request
680
headers['User-agent'] = user_agent_for_github()
681
headers["Pragma"] = "no-cache"
682
if allow_compression:
683
headers["Accept-Encoding"] = "gzip"
685
headers["Accept-Encoding"] = "identity"
688
('GET' if data is None else 'POST'),
690
accepted_errors=[200, 404])
691
request.follow_redirections = True
693
response = self.transport._perform(request)
695
if response.code == 404:
696
raise NotGitRepository()
697
elif response.code != 200:
698
raise GitProtocolError("unexpected http resp %d for %s" %
699
(response.code, url))
701
# TODO: Optimization available by adding `preload_content=False` to the
702
# request and just passing the `read` method on instead of going via
703
# `BytesIO`, if we can guarantee that the entire response is consumed
704
# before issuing the next to still allow for connection reuse from the
706
if response.getheader("Content-Encoding") == "gzip":
707
read = gzip.GzipFile(fileobj=response).read
711
class WrapResponse(object):
713
def __init__(self, response):
714
self._response = response
715
self.status = response.code
716
self.content_type = response.getheader("Content-Type")
717
self.redirect_location = response.geturl()
720
return self._response.readlines()
723
self._response.close()
725
return WrapResponse(response), read
728
class RemoteGitControlDirFormat(GitControlDirFormat):
729
"""The .git directory control format."""
731
supports_workingtrees = False
734
def _known_formats(self):
735
return set([RemoteGitControlDirFormat()])
737
def get_branch_format(self):
738
return RemoteGitBranchFormat()
740
def is_initializable(self):
743
def is_supported(self):
746
def open(self, transport, _found=None):
747
"""Open this directory.
750
# we dont grok readonly - git isn't integrated with transport.
752
if url.startswith('readonly+'):
753
url = url[len('readonly+'):]
754
scheme = urlparse.urlsplit(transport.external_url())[0]
755
if isinstance(transport, GitSmartTransport):
756
client = transport._get_client()
757
client_path = transport._get_path()
758
elif scheme in ("http", "https"):
759
client = BzrGitHttpClient(transport)
760
client_path, _ = urlutils.split_segment_parameters(transport._path)
761
elif scheme == 'file':
762
client = dulwich.client.LocalGitClient()
763
client_path = transport.local_abspath('.')
765
raise NotBranchError(transport.base)
767
pass # TODO(jelmer): Actually probe for something
768
return RemoteGitDir(transport, self, client, client_path)
770
def get_format_description(self):
771
return "Remote Git Repository"
773
def initialize_on_transport(self, transport):
774
raise UninitializableFormat(self)
776
def supports_transport(self, transport):
778
external_url = transport.external_url()
779
except InProcessTransport:
780
raise NotBranchError(path=transport.base)
781
return (external_url.startswith("http:")
782
or external_url.startswith("https:")
783
or external_url.startswith("git+")
784
or external_url.startswith("git:"))
787
class GitRemoteRevisionTree(RevisionTree):
789
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
790
"""Create an archive of this tree.
792
:param format: Format name (e.g. 'tar')
793
:param name: target file name
794
:param root: Root directory name (or None)
795
:param subdir: Subdirectory to export (or None)
796
:return: Iterator over archive chunks
798
commit = self._repository.lookup_bzr_revision_id(
799
self.get_revision_id())[0]
800
f = tempfile.SpooledTemporaryFile()
801
# git-upload-archive(1) generaly only supports refs. So let's see if we
805
self._repository.controldir.get_refs_container().as_dict().items()}
807
committish = reverse_refs[commit]
809
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
810
# Let's hope for the best.
812
self._repository.archive(
813
format, committish, f.write,
814
subdirs=([subdir] if subdir else None),
815
prefix=(root + '/') if root else '')
817
return osutils.file_iterator(f)
819
def is_versioned(self, path):
820
raise GitSmartRemoteNotSupported(self.is_versioned, self)
822
def has_filename(self, path):
823
raise GitSmartRemoteNotSupported(self.has_filename, self)
825
def get_file_text(self, path):
826
raise GitSmartRemoteNotSupported(self.get_file_text, self)
102
829
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,
833
return self.control_url
835
def get_parent_map(self, revids):
836
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
838
def archive(self, *args, **kwargs):
839
return self.controldir.archive(*args, **kwargs)
841
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
843
return self.controldir.fetch_pack(
844
determine_wants, graph_walker, pack_data, progress)
846
def send_pack(self, get_changed_refs, generate_pack_data):
847
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
849
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
851
fd, path = tempfile.mkstemp(suffix=".pack")
853
self.fetch_pack(determine_wants, graph_walker,
854
lambda x: os.write(fd, x), progress)
857
if os.path.getsize(path) == 0:
858
return EmptyObjectStoreIterator()
859
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
861
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
862
# This won't work for any round-tripped bzr revisions, but it's a
865
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
866
except InvalidRevisionId:
867
raise NoSuchRevision(self, bzr_revid)
869
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
870
"""Lookup a revision id.
874
mapping = self.get_mapping()
875
# Not really an easy way to parse foreign revids here..
876
return mapping.revision_id_foreign_to_bzr(foreign_revid)
878
def revision_tree(self, revid):
879
return GitRemoteRevisionTree(self, revid)
881
def get_revisions(self, revids):
882
raise GitSmartRemoteNotSupported(self.get_revisions, self)
884
def has_revisions(self, revids):
885
raise GitSmartRemoteNotSupported(self.get_revisions, self)
888
class RemoteGitTagDict(GitTags):
890
def set_tag(self, name, revid):
891
sha = self.branch.lookup_bzr_revision_id(revid)[0]
892
self._set_ref(name, sha)
894
def delete_tag(self, name):
895
self._set_ref(name, dulwich.client.ZERO_SHA)
897
def _set_ref(self, name, sha):
898
ref = tag_name_to_ref(name)
900
def get_changed_refs(old_refs):
902
if sha == dulwich.client.ZERO_SHA and ref not in ret:
903
raise NoSuchTag(name)
907
def generate_pack_data(have, want, ofs_delta=False):
908
return pack_objects_to_data([])
909
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
912
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)
914
def __init__(self, controldir, repository, name):
916
super(RemoteGitBranch, self).__init__(controldir, repository, name,
917
RemoteGitBranchFormat())
919
def last_revision_info(self):
920
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
924
return self.control_url
927
def control_url(self):
930
def revision_id_to_revno(self, revision_id):
931
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
933
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
934
return self.lookup_foreign_revision_id(self.head)
938
if self._sha is not None:
940
refs = self.controldir.get_refs_container()
941
name = branch_name_to_ref(self.name)
943
self._sha = refs[name]
945
raise NoSuchRef(name, self.repository.user_url, refs)
948
def _synchronize_history(self, destination, revision_id):
949
"""See Branch._synchronize_history()."""
950
if revision_id is None:
951
revision_id = self.last_revision()
952
destination.generate_revision_history(revision_id)
954
def _get_parent_location(self):
957
def get_push_location(self):
960
def set_push_location(self, url):
963
def _iter_tag_refs(self):
964
"""Iterate over the tag refs.
966
:param refs: Refs dictionary (name -> git sha1)
967
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
969
refs = self.controldir.get_refs_container()
970
for ref_name, unpeeled in refs.as_dict().items():
972
tag_name = ref_to_tag_name(ref_name)
973
except (ValueError, UnicodeDecodeError):
975
peeled = refs.get_peeled(ref_name)
977
# Let's just hope it's a commit
979
if not isinstance(tag_name, text_type):
980
raise TypeError(tag_name)
981
yield (ref_name, tag_name, peeled, unpeeled)
983
def set_last_revision_info(self, revno, revid):
984
self.generate_revision_history(revid)
986
def generate_revision_history(self, revision_id, last_rev=None,
988
sha = self.lookup_bzr_revision_id(revision_id)[0]
989
def get_changed_refs(old_refs):
990
return {self.ref: sha}
991
def generate_pack_data(have, want, ofs_delta=False):
992
return pack_objects_to_data([])
993
self.repository.send_pack(get_changed_refs, generate_pack_data)
997
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1000
for k, v in refs_dict.items():
1005
for name, target in symrefs_dict.items():
1006
base[name] = SYMREF + target
1007
ret = DictRefsContainer(base)
1008
ret._peeled = peeled