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 old_refs:
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,
831
supports_random_access = False
835
return self.control_url
837
def get_parent_map(self, revids):
838
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
840
def archive(self, *args, **kwargs):
841
return self.controldir.archive(*args, **kwargs)
843
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
845
return self.controldir.fetch_pack(
846
determine_wants, graph_walker, pack_data, progress)
848
def send_pack(self, get_changed_refs, generate_pack_data):
849
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
851
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
853
fd, path = tempfile.mkstemp(suffix=".pack")
855
self.fetch_pack(determine_wants, graph_walker,
856
lambda x: os.write(fd, x), progress)
859
if os.path.getsize(path) == 0:
860
return EmptyObjectStoreIterator()
861
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
863
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
864
# This won't work for any round-tripped bzr revisions, but it's a
867
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
868
except InvalidRevisionId:
869
raise NoSuchRevision(self, bzr_revid)
871
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
872
"""Lookup a revision id.
876
mapping = self.get_mapping()
877
# Not really an easy way to parse foreign revids here..
878
return mapping.revision_id_foreign_to_bzr(foreign_revid)
880
def revision_tree(self, revid):
881
return GitRemoteRevisionTree(self, revid)
883
def get_revisions(self, revids):
884
raise GitSmartRemoteNotSupported(self.get_revisions, self)
886
def has_revisions(self, revids):
887
raise GitSmartRemoteNotSupported(self.get_revisions, self)
890
class RemoteGitTagDict(GitTags):
892
def set_tag(self, name, revid):
893
sha = self.branch.lookup_bzr_revision_id(revid)[0]
894
self._set_ref(name, sha)
896
def delete_tag(self, name):
897
self._set_ref(name, dulwich.client.ZERO_SHA)
899
def _set_ref(self, name, sha):
900
ref = tag_name_to_ref(name)
902
def get_changed_refs(old_refs):
904
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
905
raise NoSuchTag(name)
909
def generate_pack_data(have, want, ofs_delta=False):
910
return pack_objects_to_data([])
911
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
914
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)
916
def __init__(self, controldir, repository, name):
918
super(RemoteGitBranch, self).__init__(controldir, repository, name,
919
RemoteGitBranchFormat())
921
def last_revision_info(self):
922
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
926
return self.control_url
929
def control_url(self):
932
def revision_id_to_revno(self, revision_id):
933
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
935
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
936
return self.lookup_foreign_revision_id(self.head)
940
if self._sha is not None:
942
refs = self.controldir.get_refs_container()
943
name = branch_name_to_ref(self.name)
945
self._sha = refs[name]
947
raise NoSuchRef(name, self.repository.user_url, refs)
950
def _synchronize_history(self, destination, revision_id):
951
"""See Branch._synchronize_history()."""
952
if revision_id is None:
953
revision_id = self.last_revision()
954
destination.generate_revision_history(revision_id)
956
def _get_parent_location(self):
959
def get_push_location(self):
962
def set_push_location(self, url):
965
def _iter_tag_refs(self):
966
"""Iterate over the tag refs.
968
:param refs: Refs dictionary (name -> git sha1)
969
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
971
refs = self.controldir.get_refs_container()
972
for ref_name, unpeeled in refs.as_dict().items():
974
tag_name = ref_to_tag_name(ref_name)
975
except (ValueError, UnicodeDecodeError):
977
peeled = refs.get_peeled(ref_name)
979
# Let's just hope it's a commit
981
if not isinstance(tag_name, text_type):
982
raise TypeError(tag_name)
983
yield (ref_name, tag_name, peeled, unpeeled)
985
def set_last_revision_info(self, revno, revid):
986
self.generate_revision_history(revid)
988
def generate_revision_history(self, revision_id, last_rev=None,
990
sha = self.lookup_bzr_revision_id(revision_id)[0]
991
def get_changed_refs(old_refs):
992
return {self.ref: sha}
993
def generate_pack_data(have, want, ofs_delta=False):
994
return pack_objects_to_data([])
995
self.repository.send_pack(get_changed_refs, generate_pack_data)
999
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1002
for k, v in refs_dict.items():
1007
for name, target in symrefs_dict.items():
1008
base[name] = SYMREF + target
1009
ret = DictRefsContainer(base)
1010
ret._peeled = peeled