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 ") or
195
message == 'Repository not found.' or
196
(message.startswith('Repository ') and message.endswith(' not found.'))):
197
return NotBranchError(url, message)
198
if message == "HEAD failed to update":
199
base_url, _ = urlutils.split_segment_parameters(url)
200
return HeadUpdateFailed(base_url)
201
if message.startswith('access denied or repository not exported:'):
202
extra, path = message.split(': ', 1)
203
return PermissionDenied(path, extra)
204
if message.endswith('You are not allowed to push code to this project.'):
205
return PermissionDenied(url, message)
206
if message.endswith(' does not appear to be a git repository'):
207
return NotBranchError(url, message)
208
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
210
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
211
# Don't know, just return it to the user as-is
212
return RemoteGitError(message)
38
215
class GitSmartTransport(Transport):
40
217
def __init__(self, url, _client=None):
41
218
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():
219
(self._host, self._port, self._username, self._path) = \
221
if 'transport' in debug.debug_flags:
222
trace.mutter('host: %r, user: %r, port: %r, path: %r',
223
self._host, self._username, self._port, self._path)
224
self._client = _client
225
self._stripped_path = self._path.rsplit(",", 1)[0]
227
def external_url(self):
230
def has(self, relpath):
233
def _get_client(self):
234
raise NotImplementedError(self._get_client)
237
return self._stripped_path
69
239
def get(self, path):
70
240
raise NoSuchFile(path)
242
def abspath(self, relpath):
243
return urlutils.join(self.base, relpath)
72
245
def clone(self, offset=None):
73
246
"""See Transport.clone()."""
74
247
if offset is None:
77
250
newurl = urlutils.join(self.base, offset)
79
return GitSmartTransport(newurl, self._client)
252
return self.__class__(newurl, self._client)
255
class TCPGitSmartTransport(GitSmartTransport):
259
def _get_client(self):
260
if self._client is not None:
265
# return dulwich.client.LocalGitClient()
266
return dulwich.client.SubprocessGitClient()
267
return dulwich.client.TCPGitClient(self._host, self._port,
268
report_activity=self._report_activity)
271
class SSHSocketWrapper(object):
273
def __init__(self, sock):
276
def read(self, len=None):
277
return self.sock.recv(len)
279
def write(self, data):
280
return self.sock.write(data)
283
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
286
class DulwichSSHVendor(dulwich.client.SSHVendor):
289
from ..transport import ssh
290
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
292
def run_command(self, host, command, username=None, port=None):
293
connection = self.bzr_ssh_vendor.connect_ssh(username=username,
294
password=None, port=port, host=host, command=command)
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(self._host, self._port, self._username,
322
report_activity=self._report_activity)
323
# Set up alternate pack program paths
324
upload_pack = location_config.get_user_option('git_upload_pack')
326
client.alternative_paths["upload-pack"] = upload_pack
327
receive_pack = location_config.get_user_option('git_receive_pack')
329
client.alternative_paths["receive-pack"] = receive_pack
333
class RemoteGitBranchFormat(GitBranchFormat):
335
def get_format_description(self):
336
return 'Remote Git Branch'
339
def _matchingcontroldir(self):
340
return RemoteGitControlDirFormat()
342
def initialize(self, a_controldir, name=None, repository=None,
343
append_revisions_only=None):
344
raise UninitializableFormat(self)
347
class DefaultProgressReporter(object):
349
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
350
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
352
def __init__(self, pb):
355
def progress(self, text):
356
text = text.rstrip(b"\r\n")
357
text = text.decode('utf-8')
358
if text.lower().startswith('error: '):
359
trace.show_error('git: %s', text[len(b'error: '):])
361
trace.mutter("git: %s", text)
362
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
364
(text, pct, current, total) = g.groups()
365
self.pb.update(text, int(current), int(total))
367
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
369
(text, total) = g.groups()
370
self.pb.update(text, None, int(total))
372
trace.note("%s", text)
82
375
class RemoteGitDir(GitDir):
84
def __init__(self, transport, lockfiles, format):
377
def __init__(self, transport, format, client, client_path):
85
378
self._format = format
86
379
self.root_transport = transport
87
380
self.transport = transport
88
self._lockfiles = lockfiles
381
self._mode_check_done = None
382
self._client = client
383
self._client_path = client_path
384
self.base = self.root_transport.base
388
def _gitrepository_class(self):
389
return RemoteGitRepository
391
def archive(self, format, committish, write_data, progress=None,
392
write_error=None, subdirs=None, prefix=None):
394
pb = ui.ui_factory.nested_progress_bar()
395
progress = DefaultProgressReporter(pb).progress
398
def progress_wrapper(message):
399
if message.startswith(b"fatal: Unknown archive format \'"):
400
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
401
raise errors.NoSuchExportFormat(format.decode('ascii'))
402
return progress(message)
404
self._client.archive(
405
self._client_path, committish, write_data, progress_wrapper,
407
format=(format.encode('ascii') if format else None),
409
prefix=(prefix.encode('utf-8') if prefix else None))
410
except GitProtocolError as e:
411
raise parse_git_error(self.transport.external_url(), e)
416
def fetch_pack(self, determine_wants, graph_walker, pack_data,
419
pb = ui.ui_factory.nested_progress_bar()
420
progress = DefaultProgressReporter(pb).progress
424
result = self._client.fetch_pack(
425
self._client_path, determine_wants, graph_walker, pack_data,
427
if result.refs is None:
429
self._refs = remote_refs_dict_to_container(
430
result.refs, result.symrefs)
432
except GitProtocolError as e:
433
raise parse_git_error(self.transport.external_url(), e)
438
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
440
pb = ui.ui_factory.nested_progress_bar()
441
progress = DefaultProgressReporter(pb).progress
445
def get_changed_refs_wrapper(refs):
446
# TODO(jelmer): This drops symref information
447
self._refs = remote_refs_dict_to_container(refs)
448
return get_changed_refs(refs)
450
return self._client.send_pack(
451
self._client_path, get_changed_refs_wrapper,
452
generate_pack_data, progress)
453
except GitProtocolError as e:
454
raise parse_git_error(self.transport.external_url(), e)
459
def create_branch(self, name=None, repository=None,
460
append_revisions_only=None, ref=None):
461
refname = self._get_selected_ref(name, ref)
462
if refname != b'HEAD' and refname in self.get_refs_container():
463
raise AlreadyBranchError(self.user_url)
464
if refname in self.get_refs_container():
465
ref_chain, unused_sha = self.get_refs_container().follow(self._get_selected_ref(None))
466
if ref_chain[0] == b'HEAD':
467
refname = ref_chain[1]
468
repo = self.open_repository()
469
return RemoteGitBranch(self, repo, refname)
471
def destroy_branch(self, name=None):
472
refname = self._get_selected_ref(name)
473
def get_changed_refs(old_refs):
475
if not refname in ret:
476
raise NotBranchError(self.user_url)
477
ret[refname] = dulwich.client.ZERO_SHA
479
def generate_pack_data(have, want, ofs_delta=False):
480
return pack_objects_to_data([])
481
self.send_pack(get_changed_refs, generate_pack_data)
485
return self.control_url
488
def user_transport(self):
489
return self.root_transport
492
def control_url(self):
493
return self.control_transport.base
496
def control_transport(self):
497
return self.root_transport
90
499
def open_repository(self):
91
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
500
return RemoteGitRepository(self)
502
def get_branch_reference(self, name=None):
503
ref = branch_name_to_ref(name)
504
val = self.get_refs_container().read_ref(ref)
505
if val.startswith(SYMREF):
506
return val[len(SYMREF):]
509
def open_branch(self, name=None, unsupported=False,
510
ignore_fallbacks=False, ref=None, possible_transports=None,
94
512
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
513
ref = self._get_selected_ref(name, ref)
515
if not nascent_ok and ref not in self.get_refs_container():
516
raise NotBranchError(self.root_transport.base,
518
except NotGitRepository:
519
raise NotBranchError(self.root_transport.base,
521
ref_chain, unused_sha = self.get_refs_container().follow(ref)
522
return RemoteGitBranch(self, repo, ref_chain[-1])
98
def open_workingtree(self):
524
def open_workingtree(self, recommend_upgrade=False):
99
525
raise NotLocalUrl(self.transport.base)
527
def has_workingtree(self):
530
def get_peeled(self, name):
531
return self.get_refs_container().get_peeled(name)
533
def get_refs_container(self):
534
if self._refs is not None:
536
result = self.fetch_pack(lambda x: None, None,
537
lambda x: None, lambda x: trace.mutter("git: %s" % x))
538
self._refs = remote_refs_dict_to_container(
539
result.refs, result.symrefs)
542
def push_branch(self, source, revision_id=None, overwrite=False,
543
remember=False, create_prefix=False, lossy=False,
545
"""Push the source branch into this ControlDir."""
546
if revision_id is None:
547
# No revision supplied by the user, default to the branch
549
revision_id = source.last_revision()
551
push_result = GitPushResult()
552
push_result.workingtree_updated = None
553
push_result.master_branch = None
554
push_result.source_branch = source
555
push_result.stacked_on = None
556
push_result.branch_push_result = None
557
repo = self.find_repository()
558
refname = self._get_selected_ref(name)
559
if isinstance(source, GitBranch) and lossy:
560
raise errors.LossyPushToSameVCS(source.controldir, self)
561
source_store = get_object_store(source.repository)
562
with source_store.lock_read():
563
def get_changed_refs(refs):
564
self._refs = remote_refs_dict_to_container(refs)
566
# TODO(jelmer): Unpeel if necessary
567
push_result.new_original_revid = revision_id
569
new_sha = source_store._lookup_revision_sha1(revision_id)
572
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
573
except errors.NoSuchRevision:
574
raise errors.NoRoundtrippingSupport(
575
source, self.open_branch(name=name, nascent_ok=True))
577
if remote_divergence(ret.get(refname), new_sha, source_store):
578
raise DivergedBranches(
579
source, self.open_branch(name, nascent_ok=True))
580
ret[refname] = new_sha
583
generate_pack_data = source_store.generate_lossy_pack_data
585
generate_pack_data = source_store.generate_pack_data
586
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
587
push_result.new_revid = repo.lookup_foreign_revision_id(
590
old_remote = self._refs[refname]
592
old_remote = ZERO_SHA
593
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
594
self._refs = remote_refs_dict_to_container(new_refs)
595
push_result.target_branch = self.open_branch(name)
596
if old_remote != ZERO_SHA:
597
push_result.branch_push_result = GitBranchPushResult()
598
push_result.branch_push_result.source_branch = source
599
push_result.branch_push_result.target_branch = push_result.target_branch
600
push_result.branch_push_result.local_branch = None
601
push_result.branch_push_result.master_branch = push_result.target_branch
602
push_result.branch_push_result.old_revid = push_result.old_revid
603
push_result.branch_push_result.new_revid = push_result.new_revid
604
push_result.branch_push_result.new_original_revid = push_result.new_original_revid
605
if source.get_push_location() is None or remember:
606
source.set_push_location(push_result.target_branch.base)
609
def _find_commondir(self):
610
# There is no way to find the commondir, if there is any.
614
class EmptyObjectStoreIterator(dict):
616
def iterobjects(self):
620
class TemporaryPackIterator(Pack):
622
def __init__(self, path, resolve_ext_ref):
623
super(TemporaryPackIterator, self).__init__(
624
path, resolve_ext_ref=resolve_ext_ref)
625
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
627
def _idx_load_or_generate(self, path):
628
if not os.path.exists(path):
629
pb = ui.ui_factory.nested_progress_bar()
631
def report_progress(cur, total):
632
pb.update("generating index", cur, total)
633
self.data.create_index(path,
634
progress=report_progress)
637
return load_pack_index(path)
640
if self._idx is not None:
642
os.remove(self._idx_path)
643
if self._data is not None:
645
os.remove(self._data_path)
648
class BzrGitHttpClient(dulwich.client.HttpGitClient):
650
def __init__(self, transport, *args, **kwargs):
651
self.transport = transport
652
super(BzrGitHttpClient, self).__init__(transport.external_url(), *args, **kwargs)
654
def _http_request(self, url, headers=None, data=None,
655
allow_compression=False):
656
"""Perform HTTP request.
658
:param url: Request URL.
659
:param headers: Optional custom headers to override defaults.
660
:param data: Request data.
661
:param allow_compression: Allow GZipped communication.
662
:return: Tuple (`response`, `read`), where response is an `urllib3`
663
response object with additional `content_type` and
664
`redirect_location` properties, and `read` is a consumable read
665
method for the response data.
667
from breezy.transport.http._urllib2_wrappers import Request
668
headers['User-agent'] = user_agent_for_github()
669
headers["Pragma"] = "no-cache"
670
if allow_compression:
671
headers["Accept-Encoding"] = "gzip"
673
headers["Accept-Encoding"] = "identity"
676
('GET' if data is None else 'POST'),
678
accepted_errors=[200, 404])
679
request.follow_redirections = True
681
response = self.transport._perform(request)
683
if response.code == 404:
684
raise NotGitRepository()
685
elif response.code != 200:
686
raise GitProtocolError("unexpected http resp %d for %s" %
687
(response.code, url))
689
# TODO: Optimization available by adding `preload_content=False` to the
690
# request and just passing the `read` method on instead of going via
691
# `BytesIO`, if we can guarantee that the entire response is consumed
692
# before issuing the next to still allow for connection reuse from the
694
if response.getheader("Content-Encoding") == "gzip":
695
read = gzip.GzipFile(fileobj=response).read
699
class WrapResponse(object):
701
def __init__(self, response):
702
self._response = response
703
self.status = response.code
704
self.content_type = response.getheader("Content-Type")
705
self.redirect_location = response.geturl()
708
return self._response.readlines()
711
self._response.close()
713
return WrapResponse(response), read
716
class RemoteGitControlDirFormat(GitControlDirFormat):
717
"""The .git directory control format."""
719
supports_workingtrees = False
722
def _known_formats(self):
723
return set([RemoteGitControlDirFormat()])
725
def get_branch_format(self):
726
return RemoteGitBranchFormat()
728
def is_initializable(self):
731
def is_supported(self):
734
def open(self, transport, _found=None):
735
"""Open this directory.
738
# we dont grok readonly - git isn't integrated with transport.
740
if url.startswith('readonly+'):
741
url = url[len('readonly+'):]
742
scheme = urlparse.urlsplit(transport.external_url())[0]
743
if isinstance(transport, GitSmartTransport):
744
client = transport._get_client()
745
client_path = transport._get_path()
746
elif scheme in ("http", "https"):
747
client = BzrGitHttpClient(transport)
748
client_path, _ = urlutils.split_segment_parameters(transport._path)
749
elif scheme == 'file':
750
client = dulwich.client.LocalGitClient()
751
client_path = transport.local_abspath('.')
753
raise NotBranchError(transport.base)
755
pass # TODO(jelmer): Actually probe for something
756
return RemoteGitDir(transport, self, client, client_path)
758
def get_format_description(self):
759
return "Remote Git Repository"
761
def initialize_on_transport(self, transport):
762
raise UninitializableFormat(self)
764
def supports_transport(self, transport):
766
external_url = transport.external_url()
767
except InProcessTransport:
768
raise NotBranchError(path=transport.base)
769
return (external_url.startswith("http:") or
770
external_url.startswith("https:") or
771
external_url.startswith("git+") or
772
external_url.startswith("git:"))
775
class GitRemoteRevisionTree(RevisionTree):
777
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
778
"""Create an archive of this tree.
780
:param format: Format name (e.g. 'tar')
781
:param name: target file name
782
:param root: Root directory name (or None)
783
:param subdir: Subdirectory to export (or None)
784
:return: Iterator over archive chunks
786
commit = self._repository.lookup_bzr_revision_id(
787
self.get_revision_id())[0]
788
f = tempfile.SpooledTemporaryFile()
789
# git-upload-archive(1) generaly only supports refs. So let's see if we
793
self._repository.controldir.get_refs_container().as_dict().items()}
795
committish = reverse_refs[commit]
797
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
798
# Let's hope for the best.
800
self._repository.archive(
801
format, committish, f.write,
802
subdirs=([subdir] if subdir else None),
803
prefix=(root+'/') if root else '')
805
return osutils.file_iterator(f)
807
def is_versioned(self, path, file_id=None):
808
raise GitSmartRemoteNotSupported(self.is_versioned, self)
810
def has_filename(self, path):
811
raise GitSmartRemoteNotSupported(self.has_filename, self)
813
def get_file_text(self, path, file_id=None):
814
raise GitSmartRemoteNotSupported(self.get_file_text, self)
102
817
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,
821
return self.control_url
823
def get_parent_map(self, revids):
824
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
826
def archive(self, *args, **kwargs):
827
return self.controldir.archive(*args, **kwargs)
829
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
831
return self.controldir.fetch_pack(
832
determine_wants, graph_walker, pack_data, progress)
834
def send_pack(self, get_changed_refs, generate_pack_data):
835
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
837
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
839
fd, path = tempfile.mkstemp(suffix=".pack")
841
self.fetch_pack(determine_wants, graph_walker,
842
lambda x: os.write(fd, x), progress)
845
if os.path.getsize(path) == 0:
846
return EmptyObjectStoreIterator()
847
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
849
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
850
# This won't work for any round-tripped bzr revisions, but it's a start..
852
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
853
except InvalidRevisionId:
854
raise NoSuchRevision(self, bzr_revid)
856
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
857
"""Lookup a revision id.
861
mapping = self.get_mapping()
862
# Not really an easy way to parse foreign revids here..
863
return mapping.revision_id_foreign_to_bzr(foreign_revid)
865
def revision_tree(self, revid):
866
return GitRemoteRevisionTree(self, revid)
868
def get_revisions(self, revids):
869
raise GitSmartRemoteNotSupported(self.get_revisions, self)
871
def has_revisions(self, revids):
872
raise GitSmartRemoteNotSupported(self.get_revisions, self)
875
class RemoteGitTagDict(GitTags):
877
def set_tag(self, name, revid):
878
sha = self.branch.lookup_bzr_revision_id(revid)[0]
879
self._set_ref(name, sha)
881
def delete_tag(self, name):
882
self._set_ref(name, dulwich.client.ZERO_SHA)
884
def _set_ref(self, name, sha):
885
ref = tag_name_to_ref(name)
886
def get_changed_refs(old_refs):
888
if sha == dulwich.client.ZERO_SHA and ref not in ret:
889
raise NoSuchTag(name)
892
def generate_pack_data(have, want, ofs_delta=False):
893
return pack_objects_to_data([])
894
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
897
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)
899
def __init__(self, controldir, repository, name):
901
super(RemoteGitBranch, self).__init__(controldir, repository, name,
902
RemoteGitBranchFormat())
904
def last_revision_info(self):
905
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
909
return self.control_url
912
def control_url(self):
915
def revision_id_to_revno(self, revision_id):
916
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
918
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
919
return self.lookup_foreign_revision_id(self.head)
923
if self._sha is not None:
925
refs = self.controldir.get_refs_container()
926
name = branch_name_to_ref(self.name)
928
self._sha = refs[name]
930
raise NoSuchRef(name, self.repository.user_url, refs)
933
def _synchronize_history(self, destination, revision_id):
934
"""See Branch._synchronize_history()."""
935
if revision_id is None:
936
revision_id = self.last_revision()
937
destination.generate_revision_history(revision_id)
939
def _get_parent_location(self):
942
def get_push_location(self):
945
def set_push_location(self, url):
948
def _iter_tag_refs(self):
949
"""Iterate over the tag refs.
951
:param refs: Refs dictionary (name -> git sha1)
952
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
954
refs = self.controldir.get_refs_container()
955
for ref_name, unpeeled in refs.as_dict().items():
957
tag_name = ref_to_tag_name(ref_name)
958
except (ValueError, UnicodeDecodeError):
960
peeled = refs.get_peeled(ref_name)
962
# Let's just hope it's a commit
964
if not isinstance(tag_name, text_type):
965
raise TypeError(tag_name)
966
yield (ref_name, tag_name, peeled, unpeeled)
968
def set_last_revision_info(self, revno, revid):
969
self.generate_revision_history(revid)
971
def generate_revision_history(self, revision_id, last_rev=None,
973
sha = self.lookup_bzr_revision_id(revision_id)[0]
974
def get_changed_refs(old_refs):
975
return {self.ref: sha}
976
def generate_pack_data(have, want, ofs_delta=False):
977
return pack_objects_to_data([])
978
self.repository.send_pack(get_changed_refs, generate_pack_data)
982
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
985
for k, v in refs_dict.items():
990
for name, target in symrefs_dict.items():
991
base[name] = SYMREF + target
992
ret = DictRefsContainer(base)