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 lazy_check_versions
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,
62
user_agent_for_github,
27
64
lazy_check_versions()
29
from bzrlib.plugins.git.branch import GitBranch
30
from bzrlib.plugins.git.errors import NoSuchRef
31
from bzrlib.plugins.git.dir import GitDir
32
from bzrlib.plugins.git.foreign import ForeignBranch
33
from bzrlib.plugins.git.repository import GitFormat, GitRepository
78
GitSmartRemoteNotSupported,
81
from .mapping import (
84
from .object_store import (
90
from .repository import (
101
import dulwich.client
102
from dulwich.errors import (
106
from dulwich.pack import (
108
pack_objects_to_data,
110
from dulwich.protocol import ZERO_SHA
111
from dulwich.refs import (
115
from dulwich.repo import (
41
from dulwich.pack import PackData, Pack, PackIndex
43
# Don't run any tests on GitSmartTransport as it is not intended to be
123
import urllib.parse as urlparse
124
from urllib.parse import splituser, splitnport
127
from urllib import splituser, splitnport
129
# urlparse only supports a limited number of schemes by default
130
register_urlparse_netloc_protocol('git')
131
register_urlparse_netloc_protocol('git+ssh')
133
from dulwich.pack import load_pack_index
136
class GitPushResult(PushResult):
138
def _lookup_revno(self, revid):
140
return _quick_lookup_revno(self.source_branch, self.target_branch,
142
except GitSmartRemoteNotSupported:
147
return self._lookup_revno(self.old_revid)
151
return self._lookup_revno(self.new_revid)
154
# Don't run any tests on GitSmartTransport as it is not intended to be
44
155
# a full implementation of Transport
45
156
def get_test_permutations():
160
def split_git_url(url):
164
:return: Tuple with host, port, username, path.
166
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
167
path = urlparse.unquote(loc)
168
if path.startswith("/~"):
170
(username, hostport) = splituser(netloc)
171
(host, port) = splitnport(hostport, None)
172
return (host, port, 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.split_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, 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
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
214
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
215
# Don't know, just return it to the user as-is
216
return RemoteGitError(message)
49
219
class GitSmartTransport(Transport):
51
221
def __init__(self, url, _client=None):
52
222
Transport.__init__(self, url)
53
(scheme, _, loc, _, _) = urlparse.urlsplit(url)
54
assert scheme == "git"
55
hostport, self._path = urllib.splithost(loc)
56
(self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
223
(self._host, self._port, self._username, self._path) = \
225
if 'transport' in debug.debug_flags:
226
trace.mutter('host: %r, user: %r, port: %r, path: %r',
227
self._host, self._username, self._port, self._path)
57
228
self._client = _client
229
self._stripped_path = self._path.rsplit(",", 1)[0]
231
def external_url(self):
234
def has(self, relpath):
59
237
def _get_client(self):
60
if self._client is not None:
64
return git.client.TCPGitClient(self._host, self._port,
65
capabilities=["multi_ack", "side-band-64k", "ofs-delta", "side-band"])
238
raise NotImplementedError(self._get_client)
67
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
70
info("git: %s" % text)
71
self._get_client().fetch_pack(self._path, determine_wants,
72
graph_walker, pack_data, progress)
241
return self._stripped_path
74
243
def get(self, path):
75
244
raise NoSuchFile(path)
85
254
newurl = urlutils.join(self.base, offset)
87
return GitSmartTransport(newurl, self._client)
256
return self.__class__(newurl, self._client)
259
class TCPGitSmartTransport(GitSmartTransport):
263
def _get_client(self):
264
if self._client is not None:
269
# return dulwich.client.LocalGitClient()
270
return dulwich.client.SubprocessGitClient()
271
return dulwich.client.TCPGitClient(
272
self._host, self._port, report_activity=self._report_activity)
275
class SSHSocketWrapper(object):
277
def __init__(self, sock):
280
def read(self, len=None):
281
return self.sock.recv(len)
283
def write(self, data):
284
return self.sock.write(data)
287
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
290
class DulwichSSHVendor(dulwich.client.SSHVendor):
293
from ..transport import ssh
294
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
296
def run_command(self, host, command, username=None, port=None):
297
connection = self.bzr_ssh_vendor.connect_ssh(
298
username=username, password=None, port=port, host=host,
300
(kind, io_object) = connection.get_sock_or_pipes()
302
return SSHSocketWrapper(io_object)
304
raise AssertionError("Unknown io object kind %r'" % kind)
307
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
310
class SSHGitSmartTransport(GitSmartTransport):
315
path = self._stripped_path
316
if path.startswith("/~/"):
320
def _get_client(self):
321
if self._client is not None:
325
location_config = config.LocationConfig(self.base)
326
client = dulwich.client.SSHGitClient(
327
self._host, self._port, self._username,
328
report_activity=self._report_activity)
329
# Set up alternate pack program paths
330
upload_pack = location_config.get_user_option('git_upload_pack')
332
client.alternative_paths["upload-pack"] = upload_pack
333
receive_pack = location_config.get_user_option('git_receive_pack')
335
client.alternative_paths["receive-pack"] = receive_pack
339
class RemoteGitBranchFormat(GitBranchFormat):
341
def get_format_description(self):
342
return 'Remote Git Branch'
345
def _matchingcontroldir(self):
346
return RemoteGitControlDirFormat()
348
def initialize(self, a_controldir, name=None, repository=None,
349
append_revisions_only=None):
350
raise UninitializableFormat(self)
353
class DefaultProgressReporter(object):
355
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
356
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
358
def __init__(self, pb):
361
def progress(self, text):
362
text = text.rstrip(b"\r\n")
363
text = text.decode('utf-8')
364
if text.lower().startswith('error: '):
365
trace.show_error('git: %s', text[len(b'error: '):])
367
trace.mutter("git: %s", text)
368
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
370
(text, pct, current, total) = g.groups()
371
self.pb.update(text, int(current), int(total))
373
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
375
(text, total) = g.groups()
376
self.pb.update(text, None, int(total))
378
trace.note("%s", text)
90
381
class RemoteGitDir(GitDir):
92
def __init__(self, transport, lockfiles, format):
383
def __init__(self, transport, format, client, client_path):
93
384
self._format = format
94
385
self.root_transport = transport
95
386
self.transport = transport
96
self._lockfiles = lockfiles
387
self._mode_check_done = None
388
self._client = client
389
self._client_path = client_path
390
self.base = self.root_transport.base
394
def _gitrepository_class(self):
395
return RemoteGitRepository
397
def archive(self, format, committish, write_data, progress=None,
398
write_error=None, subdirs=None, prefix=None):
400
pb = ui.ui_factory.nested_progress_bar()
401
progress = DefaultProgressReporter(pb).progress
404
def progress_wrapper(message):
405
if message.startswith(b"fatal: Unknown archive format \'"):
406
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
407
raise errors.NoSuchExportFormat(format.decode('ascii'))
408
return progress(message)
410
self._client.archive(
411
self._client_path, committish, write_data, progress_wrapper,
413
format=(format.encode('ascii') if format else None),
415
prefix=(prefix.encode('utf-8') if prefix else None))
416
except GitProtocolError as e:
417
raise parse_git_error(self.transport.external_url(), e)
422
def fetch_pack(self, determine_wants, graph_walker, pack_data,
425
pb = ui.ui_factory.nested_progress_bar()
426
progress = DefaultProgressReporter(pb).progress
430
result = self._client.fetch_pack(
431
self._client_path, determine_wants, graph_walker, pack_data,
433
if result.refs is None:
435
self._refs = remote_refs_dict_to_container(
436
result.refs, result.symrefs)
438
except GitProtocolError as e:
439
raise parse_git_error(self.transport.external_url(), e)
444
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
446
pb = ui.ui_factory.nested_progress_bar()
447
progress = DefaultProgressReporter(pb).progress
451
def get_changed_refs_wrapper(refs):
452
# TODO(jelmer): This drops symref information
453
self._refs = remote_refs_dict_to_container(refs)
454
return get_changed_refs(refs)
456
return self._client.send_pack(
457
self._client_path, get_changed_refs_wrapper,
458
generate_pack_data, progress)
459
except GitProtocolError as e:
460
raise parse_git_error(self.transport.external_url(), e)
465
def create_branch(self, name=None, repository=None,
466
append_revisions_only=None, ref=None):
467
refname = self._get_selected_ref(name, ref)
468
if refname != b'HEAD' and refname in self.get_refs_container():
469
raise AlreadyBranchError(self.user_url)
470
if refname in self.get_refs_container():
471
ref_chain, unused_sha = self.get_refs_container().follow(
472
self._get_selected_ref(None))
473
if ref_chain[0] == b'HEAD':
474
refname = ref_chain[1]
475
repo = self.open_repository()
476
return RemoteGitBranch(self, repo, refname)
478
def destroy_branch(self, name=None):
479
refname = self._get_selected_ref(name)
481
def get_changed_refs(old_refs):
483
if refname not in old_refs:
484
raise NotBranchError(self.user_url)
485
ret[refname] = dulwich.client.ZERO_SHA
488
def generate_pack_data(have, want, ofs_delta=False):
489
return pack_objects_to_data([])
490
self.send_pack(get_changed_refs, generate_pack_data)
494
return self.control_url
497
def user_transport(self):
498
return self.root_transport
501
def control_url(self):
502
return self.control_transport.base
505
def control_transport(self):
506
return self.root_transport
98
508
def open_repository(self):
99
return RemoteGitRepository(self, self._lockfiles)
101
def open_branch(self, _unsupported=False):
509
return RemoteGitRepository(self)
511
def get_branch_reference(self, name=None):
512
ref = branch_name_to_ref(name)
513
val = self.get_refs_container().read_ref(ref)
514
if val.startswith(SYMREF):
515
return val[len(SYMREF):]
518
def open_branch(self, name=None, unsupported=False,
519
ignore_fallbacks=False, ref=None, possible_transports=None,
102
521
repo = self.open_repository()
103
# TODO: Support for multiple branches in one bzrdir in bzrlib!
104
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
522
ref = self._get_selected_ref(name, ref)
524
if not nascent_ok and ref not in self.get_refs_container():
525
raise NotBranchError(
526
self.root_transport.base, controldir=self)
527
except NotGitRepository:
528
raise NotBranchError(self.root_transport.base,
530
ref_chain, unused_sha = self.get_refs_container().follow(ref)
531
return RemoteGitBranch(self, repo, ref_chain[-1])
106
def open_workingtree(self):
533
def open_workingtree(self, recommend_upgrade=False):
107
534
raise NotLocalUrl(self.transport.base)
536
def has_workingtree(self):
539
def get_peeled(self, name):
540
return self.get_refs_container().get_peeled(name)
542
def get_refs_container(self):
543
if self._refs is not None:
545
result = self.fetch_pack(lambda x: None, None,
547
lambda x: trace.mutter("git: %s" % x))
548
self._refs = remote_refs_dict_to_container(
549
result.refs, result.symrefs)
552
def push_branch(self, source, revision_id=None, overwrite=False,
553
remember=False, create_prefix=False, lossy=False,
555
"""Push the source branch into this ControlDir."""
556
if revision_id is None:
557
# No revision supplied by the user, default to the branch
559
revision_id = source.last_revision()
561
push_result = GitPushResult()
562
push_result.workingtree_updated = None
563
push_result.master_branch = None
564
push_result.source_branch = source
565
push_result.stacked_on = None
566
push_result.branch_push_result = None
567
repo = self.find_repository()
568
refname = self._get_selected_ref(name)
569
if isinstance(source, GitBranch) and lossy:
570
raise errors.LossyPushToSameVCS(source.controldir, self)
571
source_store = get_object_store(source.repository)
572
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
573
def get_changed_refs(refs):
574
self._refs = remote_refs_dict_to_container(refs)
576
# TODO(jelmer): Unpeel if necessary
577
push_result.new_original_revid = revision_id
579
new_sha = source_store._lookup_revision_sha1(revision_id)
582
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
583
except errors.NoSuchRevision:
584
raise errors.NoRoundtrippingSupport(
585
source, self.open_branch(name=name, nascent_ok=True))
587
if remote_divergence(ret.get(refname), new_sha,
589
raise DivergedBranches(
590
source, self.open_branch(name, nascent_ok=True))
591
ret[refname] = new_sha
593
for tagname, revid in viewitems(source.tags.get_tag_dict()):
595
new_sha = source_store._lookup_revision_sha1(revid)
598
new_sha = repo.lookup_bzr_revision_id(revid)[0]
599
except errors.NoSuchRevision:
601
ret[tag_name_to_ref(tagname)] = new_sha
603
with source_store.lock_read():
605
generate_pack_data = source_store.generate_lossy_pack_data
607
generate_pack_data = source_store.generate_pack_data
608
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
609
push_result.new_revid = repo.lookup_foreign_revision_id(
612
old_remote = self._refs[refname]
614
old_remote = ZERO_SHA
615
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
616
self._refs = remote_refs_dict_to_container(new_refs)
617
push_result.target_branch = self.open_branch(name)
618
if old_remote != ZERO_SHA:
619
push_result.branch_push_result = GitBranchPushResult()
620
push_result.branch_push_result.source_branch = source
621
push_result.branch_push_result.target_branch = (
622
push_result.target_branch)
623
push_result.branch_push_result.local_branch = None
624
push_result.branch_push_result.master_branch = (
625
push_result.target_branch)
626
push_result.branch_push_result.old_revid = push_result.old_revid
627
push_result.branch_push_result.new_revid = push_result.new_revid
628
push_result.branch_push_result.new_original_revid = (
629
push_result.new_original_revid)
630
if source.get_push_location() is None or remember:
631
source.set_push_location(push_result.target_branch.base)
634
def _find_commondir(self):
635
# There is no way to find the commondir, if there is any.
110
639
class EmptyObjectStoreIterator(dict):
116
645
class TemporaryPackIterator(Pack):
118
647
def __init__(self, path, resolve_ext_ref):
119
self.resolve_ext_ref = resolve_ext_ref
120
super(TemporaryPackIterator, self).__init__(path)
648
super(TemporaryPackIterator, self).__init__(
649
path, resolve_ext_ref=resolve_ext_ref)
650
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
124
if self._idx is None:
125
self._data.create_index_v2(self._idx_path, self.resolve_ext_ref)
126
self._idx = PackIndex(self._idx_path)
652
def _idx_load_or_generate(self, path):
653
if not os.path.exists(path):
654
pb = ui.ui_factory.nested_progress_bar()
656
def report_progress(cur, total):
657
pb.update("generating index", cur, total)
658
self.data.create_index(path,
659
progress=report_progress)
662
return load_pack_index(path)
129
664
def __del__(self):
130
os.remove(self._data_path)
131
os.remove(self._idx_path)
665
if self._idx is not None:
667
os.remove(self._idx_path)
668
if self._data is not None:
670
os.remove(self._data_path)
673
class BzrGitHttpClient(dulwich.client.HttpGitClient):
675
def __init__(self, transport, *args, **kwargs):
676
self.transport = transport
677
url = urlutils.URL.from_string(transport.external_url())
678
url.user = url.quoted_user = None
679
url.password = url.quoted_password = None
680
super(BzrGitHttpClient, self).__init__(str(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
from breezy.transport.http._urllib2_wrappers import Request
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"
704
('GET' if data is None else 'POST'),
706
accepted_errors=[200, 404])
707
request.follow_redirections = True
709
response = self.transport._perform(request)
711
if response.code == 404:
712
raise NotGitRepository()
713
elif response.code != 200:
714
raise GitProtocolError("unexpected http resp %d for %s" %
715
(response.code, url))
717
# TODO: Optimization available by adding `preload_content=False` to the
718
# request and just passing the `read` method on instead of going via
719
# `BytesIO`, if we can guarantee that the entire response is consumed
720
# before issuing the next to still allow for connection reuse from the
722
if response.getheader("Content-Encoding") == "gzip":
723
read = gzip.GzipFile(fileobj=response).read
727
class WrapResponse(object):
729
def __init__(self, response):
730
self._response = response
731
self.status = response.code
732
self.content_type = response.getheader("Content-Type")
733
self.redirect_location = response.geturl()
736
return self._response.readlines()
739
self._response.close()
741
return WrapResponse(response), read
744
class RemoteGitControlDirFormat(GitControlDirFormat):
745
"""The .git directory control format."""
747
supports_workingtrees = False
750
def _known_formats(self):
751
return set([RemoteGitControlDirFormat()])
753
def get_branch_format(self):
754
return RemoteGitBranchFormat()
756
def is_initializable(self):
759
def is_supported(self):
762
def open(self, transport, _found=None):
763
"""Open this directory.
766
# we dont grok readonly - git isn't integrated with transport.
768
if url.startswith('readonly+'):
769
url = url[len('readonly+'):]
770
scheme = urlparse.urlsplit(transport.external_url())[0]
771
if isinstance(transport, GitSmartTransport):
772
client = transport._get_client()
773
client_path = transport._get_path()
774
elif scheme in ("http", "https"):
775
client = BzrGitHttpClient(transport)
776
client_path, _ = urlutils.split_segment_parameters(transport._path)
777
elif scheme == 'file':
778
client = dulwich.client.LocalGitClient()
779
client_path = transport.local_abspath('.')
781
raise NotBranchError(transport.base)
783
pass # TODO(jelmer): Actually probe for something
784
return RemoteGitDir(transport, self, client, client_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)
134
845
class RemoteGitRepository(GitRepository):
136
def __init__(self, gitdir, lockfiles):
137
GitRepository.__init__(self, gitdir, lockfiles)
139
def fetch_pack(self, determine_wants, graph_walker, pack_data,
847
supports_random_access = False
851
return self.control_url
853
def get_parent_map(self, revids):
854
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
856
def archive(self, *args, **kwargs):
857
return self.controldir.archive(*args, **kwargs)
859
def fetch_pack(self, determine_wants, graph_walker, pack_data,
141
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
144
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref, progress=None):
861
return self.controldir.fetch_pack(
862
determine_wants, graph_walker, pack_data, progress)
864
def send_pack(self, get_changed_refs, generate_pack_data):
865
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
867
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
145
869
fd, path = tempfile.mkstemp(suffix=".pack")
146
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
871
self.fetch_pack(determine_wants, graph_walker,
872
lambda x: os.write(fd, x), progress)
148
875
if os.path.getsize(path) == 0:
149
876
return EmptyObjectStoreIterator()
150
877
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
879
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
880
# This won't work for any round-tripped bzr revisions, but it's a
883
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
884
except InvalidRevisionId:
885
raise NoSuchRevision(self, bzr_revid)
887
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
888
"""Lookup a revision id.
892
mapping = self.get_mapping()
893
# Not really an easy way to parse foreign revids here..
894
return mapping.revision_id_foreign_to_bzr(foreign_revid)
896
def revision_tree(self, revid):
897
return GitRemoteRevisionTree(self, revid)
899
def get_revisions(self, revids):
900
raise GitSmartRemoteNotSupported(self.get_revisions, self)
902
def has_revisions(self, revids):
903
raise GitSmartRemoteNotSupported(self.get_revisions, self)
906
class RemoteGitTagDict(GitTags):
908
def set_tag(self, name, revid):
909
sha = self.branch.lookup_bzr_revision_id(revid)[0]
910
self._set_ref(name, sha)
912
def delete_tag(self, name):
913
self._set_ref(name, dulwich.client.ZERO_SHA)
915
def _set_ref(self, name, sha):
916
ref = tag_name_to_ref(name)
918
def get_changed_refs(old_refs):
920
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
921
raise NoSuchTag(name)
925
def generate_pack_data(have, want, ofs_delta=False):
926
return pack_objects_to_data([])
927
self.repository.send_pack(get_changed_refs, generate_pack_data)
153
930
class RemoteGitBranch(GitBranch):
155
def __init__(self, bzrdir, repository, name, lockfiles):
156
def determine_wants(heads):
157
if not name in heads:
158
raise NoSuchRef(name)
159
self._ref = heads[name]
160
bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None,
161
lambda x: mutter("git: %s" % x))
162
super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
932
def __init__(self, controldir, repository, name):
934
super(RemoteGitBranch, self).__init__(controldir, repository, name,
935
RemoteGitBranchFormat())
937
def last_revision_info(self):
938
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
942
return self.control_url
945
def control_url(self):
948
def revision_id_to_revno(self, revision_id):
949
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
164
951
def last_revision(self):
165
return self.mapping.revision_id_foreign_to_bzr(self._ref)
952
return self.lookup_foreign_revision_id(self.head)
956
if self._sha is not None:
958
refs = self.controldir.get_refs_container()
959
name = branch_name_to_ref(self.name)
961
self._sha = refs[name]
963
raise NoSuchRef(name, self.repository.user_url, refs)
167
966
def _synchronize_history(self, destination, revision_id):
168
967
"""See Branch._synchronize_history()."""
169
destination.generate_revision_history(self.last_revision())
968
if revision_id is None:
969
revision_id = self.last_revision()
970
destination.generate_revision_history(revision_id)
972
def _get_parent_location(self):
975
def get_push_location(self):
978
def set_push_location(self, url):
981
def _iter_tag_refs(self):
982
"""Iterate over the tag refs.
984
:param refs: Refs dictionary (name -> git sha1)
985
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
987
refs = self.controldir.get_refs_container()
988
for ref_name, unpeeled in refs.as_dict().items():
990
tag_name = ref_to_tag_name(ref_name)
991
except (ValueError, UnicodeDecodeError):
993
peeled = refs.get_peeled(ref_name)
995
# Let's just hope it's a commit
997
if not isinstance(tag_name, text_type):
998
raise TypeError(tag_name)
999
yield (ref_name, tag_name, peeled, unpeeled)
1001
def set_last_revision_info(self, revno, revid):
1002
self.generate_revision_history(revid)
1004
def generate_revision_history(self, revision_id, last_rev=None,
1006
sha = self.lookup_bzr_revision_id(revision_id)[0]
1007
def get_changed_refs(old_refs):
1008
return {self.ref: sha}
1009
def generate_pack_data(have, want, ofs_delta=False):
1010
return pack_objects_to_data([])
1011
self.repository.send_pack(get_changed_refs, generate_pack_data)
1015
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1018
for k, v in refs_dict.items():
1023
for name, target in symrefs_dict.items():
1024
base[name] = SYMREF + target
1025
ret = DictRefsContainer(base)
1026
ret._peeled = peeled