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 (
55
from ..transport import (
57
register_urlparse_netloc_protocol,
63
user_agent_for_github,
79
GitSmartRemoteNotSupported,
82
from .mapping import (
85
from .object_store import (
91
from .repository import (
103
import dulwich.client
104
from dulwich.errors import (
108
from dulwich.pack import (
110
pack_objects_to_data,
112
from dulwich.protocol import ZERO_SHA
113
from dulwich.refs import (
117
from dulwich.repo import (
125
import urllib.parse as urlparse
126
from urllib.parse import splituser
129
from urllib import splituser
131
# urlparse only supports a limited number of schemes by default
132
register_urlparse_netloc_protocol('git')
133
register_urlparse_netloc_protocol('git+ssh')
135
from dulwich.pack import load_pack_index
138
class GitPushResult(PushResult):
140
def _lookup_revno(self, revid):
142
return _quick_lookup_revno(self.source_branch, self.target_branch,
144
except GitSmartRemoteNotSupported:
149
return self._lookup_revno(self.old_revid)
153
return self._lookup_revno(self.new_revid)
156
# Don't run any tests on GitSmartTransport as it is not intended to be
157
# a full implementation of Transport
158
def get_test_permutations():
162
def split_git_url(url):
166
:return: Tuple with host, port, username, path.
168
parsed_url = urlparse.urlparse(url)
169
path = urlparse.unquote(parsed_url.path)
170
if path.startswith("/~"):
172
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
175
class RemoteGitError(BzrError):
177
_fmt = "Remote server error: %(msg)s"
180
class HeadUpdateFailed(BzrError):
182
_fmt = ("Unable to update remote HEAD branch. To update the master "
183
"branch, specify the URL %(base_url)s,branch=master.")
185
def __init__(self, base_url):
186
super(HeadUpdateFailed, self).__init__()
187
self.base_url = base_url
190
def parse_git_error(url, message):
191
"""Parse a remote git server error and return a bzr exception.
193
:param url: URL of the remote repository
194
:param message: Message sent by the remote git server
196
message = str(message).strip()
197
if (message.startswith("Could not find Repository ")
198
or message == 'Repository not found.'
199
or (message.startswith('Repository ') and
200
message.endswith(' not found.'))):
201
return NotBranchError(url, message)
202
if message == "HEAD failed to update":
203
base_url = urlutils.strip_segment_parameters(url)
204
return HeadUpdateFailed(base_url)
205
if message.startswith('access denied or repository not exported:'):
206
extra, path = message.split(':', 1)
207
return PermissionDenied(path.strip(), extra)
208
if message.endswith('You are not allowed to push code to this project.'):
209
return PermissionDenied(url, message)
210
if message.endswith(' does not appear to be a git repository'):
211
return NotBranchError(url, message)
212
if re.match('(.+) is not a valid repository name',
213
message.splitlines()[0]):
214
return NotBranchError(url, message)
215
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
217
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
218
# Don't know, just return it to the user as-is
219
return RemoteGitError(message)
38
222
class GitSmartTransport(Transport):
40
224
def __init__(self, url, _client=None):
41
225
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():
226
(self._host, self._port, self._username, self._path) = \
228
if 'transport' in debug.debug_flags:
229
trace.mutter('host: %r, user: %r, port: %r, path: %r',
230
self._host, self._username, self._port, self._path)
231
self._client = _client
232
self._stripped_path = self._path.rsplit(",", 1)[0]
234
def external_url(self):
237
def has(self, relpath):
240
def _get_client(self):
241
raise NotImplementedError(self._get_client)
244
return self._stripped_path
69
246
def get(self, path):
70
247
raise NoSuchFile(path)
249
def abspath(self, relpath):
250
return urlutils.join(self.base, relpath)
72
252
def clone(self, offset=None):
73
253
"""See Transport.clone()."""
74
254
if offset is None:
77
257
newurl = urlutils.join(self.base, offset)
79
return GitSmartTransport(newurl, self._client)
259
return self.__class__(newurl, self._client)
262
class TCPGitSmartTransport(GitSmartTransport):
266
def _get_client(self):
267
if self._client is not None:
272
# return dulwich.client.LocalGitClient()
273
return dulwich.client.SubprocessGitClient()
274
return dulwich.client.TCPGitClient(
275
self._host, self._port, report_activity=self._report_activity)
278
class SSHSocketWrapper(object):
280
def __init__(self, sock):
283
def read(self, len=None):
284
return self.sock.recv(len)
286
def write(self, data):
287
return self.sock.write(data)
290
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
293
class DulwichSSHVendor(dulwich.client.SSHVendor):
296
from ..transport import ssh
297
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
299
def run_command(self, host, command, username=None, port=None):
300
connection = self.bzr_ssh_vendor.connect_ssh(
301
username=username, password=None, port=port, host=host,
303
(kind, io_object) = connection.get_sock_or_pipes()
305
return SSHSocketWrapper(io_object)
307
raise AssertionError("Unknown io object kind %r'" % kind)
310
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
313
class SSHGitSmartTransport(GitSmartTransport):
318
path = self._stripped_path
319
if path.startswith("/~/"):
323
def _get_client(self):
324
if self._client is not None:
328
location_config = config.LocationConfig(self.base)
329
client = dulwich.client.SSHGitClient(
330
self._host, self._port, self._username,
331
report_activity=self._report_activity)
332
# Set up alternate pack program paths
333
upload_pack = location_config.get_user_option('git_upload_pack')
335
client.alternative_paths["upload-pack"] = upload_pack
336
receive_pack = location_config.get_user_option('git_receive_pack')
338
client.alternative_paths["receive-pack"] = receive_pack
342
class RemoteGitBranchFormat(GitBranchFormat):
344
def get_format_description(self):
345
return 'Remote Git Branch'
348
def _matchingcontroldir(self):
349
return RemoteGitControlDirFormat()
351
def initialize(self, a_controldir, name=None, repository=None,
352
append_revisions_only=None):
353
raise UninitializableFormat(self)
356
class DefaultProgressReporter(object):
358
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
359
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
361
def __init__(self, pb):
364
def progress(self, text):
365
text = text.rstrip(b"\r\n")
366
text = text.decode('utf-8')
367
if text.lower().startswith('error: '):
368
trace.show_error('git: %s', text[len(b'error: '):])
370
trace.mutter("git: %s", text)
371
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
373
(text, pct, current, total) = g.groups()
374
self.pb.update(text, int(current), int(total))
376
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
378
(text, total) = g.groups()
379
self.pb.update(text, None, int(total))
381
trace.note("%s", text)
82
384
class RemoteGitDir(GitDir):
84
def __init__(self, transport, lockfiles, format):
386
def __init__(self, transport, format, client, client_path):
85
387
self._format = format
86
388
self.root_transport = transport
87
389
self.transport = transport
88
self._lockfiles = lockfiles
390
self._mode_check_done = None
391
self._client = client
392
self._client_path = client_path
393
self.base = self.root_transport.base
397
def _gitrepository_class(self):
398
return RemoteGitRepository
400
def archive(self, format, committish, write_data, progress=None,
401
write_error=None, subdirs=None, prefix=None):
403
pb = ui.ui_factory.nested_progress_bar()
404
progress = DefaultProgressReporter(pb).progress
407
def progress_wrapper(message):
408
if message.startswith(b"fatal: Unknown archive format \'"):
409
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
410
raise errors.NoSuchExportFormat(format.decode('ascii'))
411
return progress(message)
413
self._client.archive(
414
self._client_path, committish, write_data, progress_wrapper,
416
format=(format.encode('ascii') if format else None),
418
prefix=(prefix.encode('utf-8') if prefix else None))
419
except GitProtocolError as e:
420
raise parse_git_error(self.transport.external_url(), e)
425
def fetch_pack(self, determine_wants, graph_walker, pack_data,
428
pb = ui.ui_factory.nested_progress_bar()
429
progress = DefaultProgressReporter(pb).progress
433
result = self._client.fetch_pack(
434
self._client_path, determine_wants, graph_walker, pack_data,
436
if result.refs is None:
438
self._refs = remote_refs_dict_to_container(
439
result.refs, result.symrefs)
441
except GitProtocolError as e:
442
raise parse_git_error(self.transport.external_url(), e)
447
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
449
pb = ui.ui_factory.nested_progress_bar()
450
progress = DefaultProgressReporter(pb).progress
454
def get_changed_refs_wrapper(refs):
455
# TODO(jelmer): This drops symref information
456
self._refs = remote_refs_dict_to_container(refs)
457
return get_changed_refs(refs)
459
return self._client.send_pack(
460
self._client_path, get_changed_refs_wrapper,
461
generate_pack_data, progress)
462
except GitProtocolError as e:
463
raise parse_git_error(self.transport.external_url(), e)
468
def create_branch(self, name=None, repository=None,
469
append_revisions_only=None, ref=None):
470
refname = self._get_selected_ref(name, ref)
471
if refname != b'HEAD' and refname in self.get_refs_container():
472
raise AlreadyBranchError(self.user_url)
473
if refname in self.get_refs_container():
474
ref_chain, unused_sha = self.get_refs_container().follow(
475
self._get_selected_ref(None))
476
if ref_chain[0] == b'HEAD':
477
refname = ref_chain[1]
478
repo = self.open_repository()
479
return RemoteGitBranch(self, repo, refname)
481
def destroy_branch(self, name=None):
482
refname = self._get_selected_ref(name)
484
def get_changed_refs(old_refs):
486
if refname not in old_refs:
487
raise NotBranchError(self.user_url)
488
ret[refname] = dulwich.client.ZERO_SHA
491
def generate_pack_data(have, want, ofs_delta=False):
492
return pack_objects_to_data([])
493
self.send_pack(get_changed_refs, generate_pack_data)
497
return self.control_url
500
def user_transport(self):
501
return self.root_transport
504
def control_url(self):
505
return self.control_transport.base
508
def control_transport(self):
509
return self.root_transport
90
511
def open_repository(self):
91
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
512
return RemoteGitRepository(self)
514
def get_branch_reference(self, name=None):
515
ref = branch_name_to_ref(name)
516
val = self.get_refs_container().read_ref(ref)
517
if val.startswith(SYMREF):
518
return val[len(SYMREF):]
521
def open_branch(self, name=None, unsupported=False,
522
ignore_fallbacks=False, ref=None, possible_transports=None,
94
524
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
525
ref = self._get_selected_ref(name, ref)
527
if not nascent_ok and ref not in self.get_refs_container():
528
raise NotBranchError(
529
self.root_transport.base, controldir=self)
530
except NotGitRepository:
531
raise NotBranchError(self.root_transport.base,
533
ref_chain, unused_sha = self.get_refs_container().follow(ref)
534
return RemoteGitBranch(self, repo, ref_chain[-1])
98
def open_workingtree(self):
536
def open_workingtree(self, recommend_upgrade=False):
99
537
raise NotLocalUrl(self.transport.base)
539
def has_workingtree(self):
542
def get_peeled(self, name):
543
return self.get_refs_container().get_peeled(name)
545
def get_refs_container(self):
546
if self._refs is not None:
548
result = self.fetch_pack(lambda x: None, None,
550
lambda x: trace.mutter("git: %s" % x))
551
self._refs = remote_refs_dict_to_container(
552
result.refs, result.symrefs)
555
def push_branch(self, source, revision_id=None, overwrite=False,
556
remember=False, create_prefix=False, lossy=False,
558
"""Push the source branch into this ControlDir."""
559
if revision_id is None:
560
# No revision supplied by the user, default to the branch
562
revision_id = source.last_revision()
564
push_result = GitPushResult()
565
push_result.workingtree_updated = None
566
push_result.master_branch = None
567
push_result.source_branch = source
568
push_result.stacked_on = None
569
push_result.branch_push_result = None
570
repo = self.find_repository()
571
refname = self._get_selected_ref(name)
572
if isinstance(source, GitBranch) and lossy:
573
raise errors.LossyPushToSameVCS(source.controldir, self)
574
source_store = get_object_store(source.repository)
575
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
576
def get_changed_refs(refs):
577
self._refs = remote_refs_dict_to_container(refs)
579
# TODO(jelmer): Unpeel if necessary
580
push_result.new_original_revid = revision_id
582
new_sha = source_store._lookup_revision_sha1(revision_id)
585
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
586
except errors.NoSuchRevision:
587
raise errors.NoRoundtrippingSupport(
588
source, self.open_branch(name=name, nascent_ok=True))
590
if remote_divergence(ret.get(refname), new_sha,
592
raise DivergedBranches(
593
source, self.open_branch(name, nascent_ok=True))
594
ret[refname] = new_sha
596
for tagname, revid in viewitems(source.tags.get_tag_dict()):
598
new_sha = source_store._lookup_revision_sha1(revid)
601
new_sha = repo.lookup_bzr_revision_id(revid)[0]
602
except errors.NoSuchRevision:
604
ret[tag_name_to_ref(tagname)] = new_sha
606
with source_store.lock_read():
608
generate_pack_data = source_store.generate_lossy_pack_data
610
generate_pack_data = source_store.generate_pack_data
611
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
612
push_result.new_revid = repo.lookup_foreign_revision_id(
615
old_remote = self._refs[refname]
617
old_remote = ZERO_SHA
618
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
619
self._refs = remote_refs_dict_to_container(new_refs)
620
push_result.target_branch = self.open_branch(name)
621
if old_remote != ZERO_SHA:
622
push_result.branch_push_result = GitBranchPushResult()
623
push_result.branch_push_result.source_branch = source
624
push_result.branch_push_result.target_branch = (
625
push_result.target_branch)
626
push_result.branch_push_result.local_branch = None
627
push_result.branch_push_result.master_branch = (
628
push_result.target_branch)
629
push_result.branch_push_result.old_revid = push_result.old_revid
630
push_result.branch_push_result.new_revid = push_result.new_revid
631
push_result.branch_push_result.new_original_revid = (
632
push_result.new_original_revid)
633
if source.get_push_location() is None or remember:
634
source.set_push_location(push_result.target_branch.base)
637
def _find_commondir(self):
638
# There is no way to find the commondir, if there is any.
642
class EmptyObjectStoreIterator(dict):
644
def iterobjects(self):
648
class TemporaryPackIterator(Pack):
650
def __init__(self, path, resolve_ext_ref):
651
super(TemporaryPackIterator, self).__init__(
652
path, resolve_ext_ref=resolve_ext_ref)
653
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
655
def _idx_load_or_generate(self, path):
656
if not os.path.exists(path):
657
with ui.ui_factory.nested_progress_bar() as pb:
658
def report_progress(cur, total):
659
pb.update("generating index", cur, total)
660
self.data.create_index(path, progress=report_progress)
661
return load_pack_index(path)
664
if self._idx is not None:
666
os.remove(self._idx_path)
667
if self._data is not None:
669
os.remove(self._data_path)
672
class BzrGitHttpClient(dulwich.client.HttpGitClient):
674
def __init__(self, transport, *args, **kwargs):
675
self.transport = transport
676
url = urlutils.URL.from_string(transport.external_url())
677
url.user = url.quoted_user = None
678
url.password = url.quoted_password = None
679
url = urlutils.strip_segment_parameters(str(url))
680
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
682
def _http_request(self, url, headers=None, data=None,
683
allow_compression=False):
684
"""Perform HTTP request.
686
:param url: Request URL.
687
:param headers: Optional custom headers to override defaults.
688
:param data: Request data.
689
:param allow_compression: Allow GZipped communication.
690
:return: Tuple (`response`, `read`), where response is an `urllib3`
691
response object with additional `content_type` and
692
`redirect_location` properties, and `read` is a consumable read
693
method for the response data.
695
if is_github_url(url):
696
headers['User-agent'] = user_agent_for_github()
697
headers["Pragma"] = "no-cache"
698
if allow_compression:
699
headers["Accept-Encoding"] = "gzip"
701
headers["Accept-Encoding"] = "identity"
703
response = self.transport.request(
704
('GET' if data is None else 'POST'),
707
headers=headers, retries=8)
709
if response.status == 404:
710
raise NotGitRepository()
711
elif response.status != 200:
712
raise GitProtocolError("unexpected http resp %d for %s" %
713
(response.code, url))
715
# TODO: Optimization available by adding `preload_content=False` to the
716
# request and just passing the `read` method on instead of going via
717
# `BytesIO`, if we can guarantee that the entire response is consumed
718
# before issuing the next to still allow for connection reuse from the
720
if response.getheader("Content-Encoding") == "gzip":
721
read = gzip.GzipFile(fileobj=response).read
725
class WrapResponse(object):
727
def __init__(self, response):
728
self._response = response
729
self.status = response.status
730
self.content_type = response.getheader("Content-Type")
731
self.redirect_location = response._actual.geturl()
734
return self._response.readlines()
739
return WrapResponse(response), read
742
def _git_url_and_path_from_transport(external_url):
743
url = urlutils.strip_segment_parameters(external_url)
744
return urlparse.urlsplit(url)
747
class RemoteGitControlDirFormat(GitControlDirFormat):
748
"""The .git directory control format."""
750
supports_workingtrees = False
753
def _known_formats(self):
754
return set([RemoteGitControlDirFormat()])
756
def get_branch_format(self):
757
return RemoteGitBranchFormat()
760
def repository_format(self):
761
return GitRepositoryFormat()
763
def is_initializable(self):
766
def is_supported(self):
769
def open(self, transport, _found=None):
770
"""Open this directory.
773
split_url = _git_url_and_path_from_transport(transport.external_url())
774
if isinstance(transport, GitSmartTransport):
775
client = transport._get_client()
776
elif split_url.scheme in ("http", "https"):
777
client = BzrGitHttpClient(transport)
778
elif split_url.scheme in ('file', ):
779
client = dulwich.client.LocalGitClient()
781
raise NotBranchError(transport.base)
783
pass # TODO(jelmer): Actually probe for something
784
return RemoteGitDir(transport, self, client, split_url.path)
786
def get_format_description(self):
787
return "Remote Git Repository"
789
def initialize_on_transport(self, transport):
790
raise UninitializableFormat(self)
792
def supports_transport(self, transport):
794
external_url = transport.external_url()
795
except InProcessTransport:
796
raise NotBranchError(path=transport.base)
797
return (external_url.startswith("http:")
798
or external_url.startswith("https:")
799
or external_url.startswith("git+")
800
or external_url.startswith("git:"))
803
class GitRemoteRevisionTree(RevisionTree):
805
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
806
"""Create an archive of this tree.
808
:param format: Format name (e.g. 'tar')
809
:param name: target file name
810
:param root: Root directory name (or None)
811
:param subdir: Subdirectory to export (or None)
812
:return: Iterator over archive chunks
814
commit = self._repository.lookup_bzr_revision_id(
815
self.get_revision_id())[0]
816
f = tempfile.SpooledTemporaryFile()
817
# git-upload-archive(1) generaly only supports refs. So let's see if we
821
self._repository.controldir.get_refs_container().as_dict().items()}
823
committish = reverse_refs[commit]
825
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
826
# Let's hope for the best.
828
self._repository.archive(
829
format, committish, f.write,
830
subdirs=([subdir] if subdir else None),
831
prefix=(root + '/') if root else '')
833
return osutils.file_iterator(f)
835
def is_versioned(self, path):
836
raise GitSmartRemoteNotSupported(self.is_versioned, self)
838
def has_filename(self, path):
839
raise GitSmartRemoteNotSupported(self.has_filename, self)
841
def get_file_text(self, path):
842
raise GitSmartRemoteNotSupported(self.get_file_text, self)
844
def list_files(self, include_root=False, from_dir=None, recursive=True):
845
raise GitSmartRemoteNotSupported(self.list_files, self)
102
848
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,
850
supports_random_access = False
854
return self.control_url
856
def get_parent_map(self, revids):
857
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
859
def archive(self, *args, **kwargs):
860
return self.controldir.archive(*args, **kwargs)
862
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
864
return self.controldir.fetch_pack(
865
determine_wants, graph_walker, pack_data, progress)
867
def send_pack(self, get_changed_refs, generate_pack_data):
868
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
870
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
872
fd, path = tempfile.mkstemp(suffix=".pack")
874
self.fetch_pack(determine_wants, graph_walker,
875
lambda x: os.write(fd, x), progress)
878
if os.path.getsize(path) == 0:
879
return EmptyObjectStoreIterator()
880
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
882
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
883
# This won't work for any round-tripped bzr revisions, but it's a
886
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
887
except InvalidRevisionId:
888
raise NoSuchRevision(self, bzr_revid)
890
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
891
"""Lookup a revision id.
895
mapping = self.get_mapping()
896
# Not really an easy way to parse foreign revids here..
897
return mapping.revision_id_foreign_to_bzr(foreign_revid)
899
def revision_tree(self, revid):
900
return GitRemoteRevisionTree(self, revid)
902
def get_revisions(self, revids):
903
raise GitSmartRemoteNotSupported(self.get_revisions, self)
905
def has_revisions(self, revids):
906
raise GitSmartRemoteNotSupported(self.get_revisions, self)
909
class RemoteGitTagDict(GitTags):
911
def set_tag(self, name, revid):
912
sha = self.branch.lookup_bzr_revision_id(revid)[0]
913
self._set_ref(name, sha)
915
def delete_tag(self, name):
916
self._set_ref(name, dulwich.client.ZERO_SHA)
918
def _set_ref(self, name, sha):
919
ref = tag_name_to_ref(name)
921
def get_changed_refs(old_refs):
923
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
924
raise NoSuchTag(name)
928
def generate_pack_data(have, want, ofs_delta=False):
929
return pack_objects_to_data([])
930
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
933
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)
935
def __init__(self, controldir, repository, name):
937
super(RemoteGitBranch, self).__init__(controldir, repository, name,
938
RemoteGitBranchFormat())
940
def last_revision_info(self):
941
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
945
return self.control_url
948
def control_url(self):
951
def revision_id_to_revno(self, revision_id):
952
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
954
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
955
return self.lookup_foreign_revision_id(self.head)
959
if self._sha is not None:
961
refs = self.controldir.get_refs_container()
962
name = branch_name_to_ref(self.name)
964
self._sha = refs[name]
966
raise NoSuchRef(name, self.repository.user_url, refs)
969
def _synchronize_history(self, destination, revision_id):
970
"""See Branch._synchronize_history()."""
971
if revision_id is None:
972
revision_id = self.last_revision()
973
destination.generate_revision_history(revision_id)
975
def _get_parent_location(self):
978
def get_push_location(self):
981
def set_push_location(self, url):
984
def _iter_tag_refs(self):
985
"""Iterate over the tag refs.
987
:param refs: Refs dictionary (name -> git sha1)
988
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
990
refs = self.controldir.get_refs_container()
991
for ref_name, unpeeled in refs.as_dict().items():
993
tag_name = ref_to_tag_name(ref_name)
994
except (ValueError, UnicodeDecodeError):
996
peeled = refs.get_peeled(ref_name)
998
# Let's just hope it's a commit
1000
if not isinstance(tag_name, text_type):
1001
raise TypeError(tag_name)
1002
yield (ref_name, tag_name, peeled, unpeeled)
1004
def set_last_revision_info(self, revno, revid):
1005
self.generate_revision_history(revid)
1007
def generate_revision_history(self, revision_id, last_rev=None,
1009
sha = self.lookup_bzr_revision_id(revision_id)[0]
1010
def get_changed_refs(old_refs):
1011
return {self.ref: sha}
1012
def generate_pack_data(have, want, ofs_delta=False):
1013
return pack_objects_to_data([])
1014
self.repository.send_pack(get_changed_refs, generate_pack_data)
1018
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1021
for k, v in refs_dict.items():
1026
for name, target in symrefs_dict.items():
1027
base[name] = SYMREF + target
1028
ret = DictRefsContainer(base)
1029
ret._peeled = peeled