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
22
from io import BytesIO
37
from ..errors import (
49
UninitializableFormat,
51
from ..revision import NULL_REVISION
52
from ..revisiontree import RevisionTree
53
from ..sixish import (
57
from ..transport import (
59
register_urlparse_netloc_protocol,
65
user_agent_for_github,
27
67
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
81
GitSmartRemoteNotSupported,
84
from .mapping import (
87
from .object_store import (
93
from .repository import (
105
import dulwich.client
106
from dulwich.errors import (
110
from dulwich.pack import (
112
pack_objects_to_data,
114
from dulwich.protocol import ZERO_SHA
115
from dulwich.refs import (
119
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
127
import urllib.parse as urlparse
128
from urllib.parse import splituser
131
from urllib import splituser
133
# urlparse only supports a limited number of schemes by default
134
register_urlparse_netloc_protocol('git')
135
register_urlparse_netloc_protocol('git+ssh')
137
from dulwich.pack import load_pack_index
140
class GitPushResult(PushResult):
142
def _lookup_revno(self, revid):
144
return _quick_lookup_revno(self.source_branch, self.target_branch,
146
except GitSmartRemoteNotSupported:
151
return self._lookup_revno(self.old_revid)
155
return self._lookup_revno(self.new_revid)
158
# Don't run any tests on GitSmartTransport as it is not intended to be
44
159
# a full implementation of Transport
45
160
def get_test_permutations():
164
def split_git_url(url):
168
:return: Tuple with host, port, username, path.
170
parsed_url = urlparse.urlparse(url)
171
path = urlparse.unquote(parsed_url.path)
172
if path.startswith("/~"):
174
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
177
class RemoteGitError(BzrError):
179
_fmt = "Remote server error: %(msg)s"
182
class HeadUpdateFailed(BzrError):
184
_fmt = ("Unable to update remote HEAD branch. To update the master "
185
"branch, specify the URL %(base_url)s,branch=master.")
187
def __init__(self, base_url):
188
super(HeadUpdateFailed, self).__init__()
189
self.base_url = base_url
192
def parse_git_error(url, message):
193
"""Parse a remote git server error and return a bzr exception.
195
:param url: URL of the remote repository
196
:param message: Message sent by the remote git server
198
message = str(message).strip()
199
if (message.startswith("Could not find Repository ")
200
or message == 'Repository not found.'
201
or (message.startswith('Repository ') and
202
message.endswith(' not found.'))):
203
return NotBranchError(url, message)
204
if message == "HEAD failed to update":
205
base_url = urlutils.strip_segment_parameters(url)
206
return HeadUpdateFailed(base_url)
207
if message.startswith('access denied or repository not exported:'):
208
extra, path = message.split(':', 1)
209
return PermissionDenied(path.strip(), extra)
210
if message.endswith('You are not allowed to push code to this project.'):
211
return PermissionDenied(url, message)
212
if message.endswith(' does not appear to be a git repository'):
213
return NotBranchError(url, message)
214
if re.match('(.+) is not a valid repository name',
215
message.splitlines()[0]):
216
return NotBranchError(url, message)
217
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
219
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
220
# Don't know, just return it to the user as-is
221
return RemoteGitError(message)
49
224
class GitSmartTransport(Transport):
51
226
def __init__(self, url, _client=None):
52
227
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)
228
(self._host, self._port, self._username, self._path) = \
230
if 'transport' in debug.debug_flags:
231
trace.mutter('host: %r, user: %r, port: %r, path: %r',
232
self._host, self._username, self._port, self._path)
57
233
self._client = _client
234
self._stripped_path = self._path.rsplit(",", 1)[0]
236
def external_url(self):
239
def has(self, relpath):
59
242
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"])
243
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)
246
return self._stripped_path
74
248
def get(self, path):
75
249
raise NoSuchFile(path)
85
259
newurl = urlutils.join(self.base, offset)
87
return GitSmartTransport(newurl, self._client)
261
return self.__class__(newurl, self._client)
264
class TCPGitSmartTransport(GitSmartTransport):
268
def _get_client(self):
269
if self._client is not None:
274
# return dulwich.client.LocalGitClient()
275
return dulwich.client.SubprocessGitClient()
276
return dulwich.client.TCPGitClient(
277
self._host, self._port, report_activity=self._report_activity)
280
class SSHSocketWrapper(object):
282
def __init__(self, sock):
285
def read(self, len=None):
286
return self.sock.recv(len)
288
def write(self, data):
289
return self.sock.write(data)
292
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
295
class DulwichSSHVendor(dulwich.client.SSHVendor):
298
from ..transport import ssh
299
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
301
def run_command(self, host, command, username=None, port=None):
302
connection = self.bzr_ssh_vendor.connect_ssh(
303
username=username, password=None, port=port, host=host,
305
(kind, io_object) = connection.get_sock_or_pipes()
307
return SSHSocketWrapper(io_object)
309
raise AssertionError("Unknown io object kind %r'" % kind)
312
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
315
class SSHGitSmartTransport(GitSmartTransport):
320
path = self._stripped_path
321
if path.startswith("/~/"):
325
def _get_client(self):
326
if self._client is not None:
330
location_config = config.LocationConfig(self.base)
331
client = dulwich.client.SSHGitClient(
332
self._host, self._port, self._username,
333
report_activity=self._report_activity)
334
# Set up alternate pack program paths
335
upload_pack = location_config.get_user_option('git_upload_pack')
337
client.alternative_paths["upload-pack"] = upload_pack
338
receive_pack = location_config.get_user_option('git_receive_pack')
340
client.alternative_paths["receive-pack"] = receive_pack
344
class RemoteGitBranchFormat(GitBranchFormat):
346
def get_format_description(self):
347
return 'Remote Git Branch'
350
def _matchingcontroldir(self):
351
return RemoteGitControlDirFormat()
353
def initialize(self, a_controldir, name=None, repository=None,
354
append_revisions_only=None):
355
raise UninitializableFormat(self)
358
class DefaultProgressReporter(object):
360
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
361
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
363
def __init__(self, pb):
366
def progress(self, text):
367
text = text.rstrip(b"\r\n")
368
text = text.decode('utf-8')
369
if text.lower().startswith('error: '):
370
trace.show_error('git: %s', text[len(b'error: '):])
372
trace.mutter("git: %s", text)
373
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
375
(text, pct, current, total) = g.groups()
376
self.pb.update(text, int(current), int(total))
378
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
380
(text, total) = g.groups()
381
self.pb.update(text, None, int(total))
383
trace.note("%s", text)
90
386
class RemoteGitDir(GitDir):
92
def __init__(self, transport, lockfiles, format):
388
def __init__(self, transport, format, client, client_path):
93
389
self._format = format
94
390
self.root_transport = transport
95
391
self.transport = transport
96
self._lockfiles = lockfiles
392
self._mode_check_done = None
393
self._client = client
394
self._client_path = client_path
395
self.base = self.root_transport.base
399
def _gitrepository_class(self):
400
return RemoteGitRepository
402
def archive(self, format, committish, write_data, progress=None,
403
write_error=None, subdirs=None, prefix=None):
405
pb = ui.ui_factory.nested_progress_bar()
406
progress = DefaultProgressReporter(pb).progress
409
def progress_wrapper(message):
410
if message.startswith(b"fatal: Unknown archive format \'"):
411
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
412
raise errors.NoSuchExportFormat(format.decode('ascii'))
413
return progress(message)
415
self._client.archive(
416
self._client_path, committish, write_data, progress_wrapper,
418
format=(format.encode('ascii') if format else None),
420
prefix=(prefix.encode('utf-8') if prefix else None))
421
except GitProtocolError as e:
422
raise parse_git_error(self.transport.external_url(), e)
427
def fetch_pack(self, determine_wants, graph_walker, pack_data,
430
pb = ui.ui_factory.nested_progress_bar()
431
progress = DefaultProgressReporter(pb).progress
435
result = self._client.fetch_pack(
436
self._client_path, determine_wants, graph_walker, pack_data,
438
if result.refs is None:
440
self._refs = remote_refs_dict_to_container(
441
result.refs, result.symrefs)
443
except GitProtocolError as e:
444
raise parse_git_error(self.transport.external_url(), e)
449
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
451
pb = ui.ui_factory.nested_progress_bar()
452
progress = DefaultProgressReporter(pb).progress
456
def get_changed_refs_wrapper(remote_refs):
457
if self._refs is not None:
458
update_refs_container(self._refs, remote_refs)
459
return get_changed_refs(remote_refs)
461
return self._client.send_pack(
462
self._client_path, get_changed_refs_wrapper,
463
generate_pack_data, progress)
464
except GitProtocolError as e:
465
raise parse_git_error(self.transport.external_url(), e)
470
def create_branch(self, name=None, repository=None,
471
append_revisions_only=None, ref=None):
472
refname = self._get_selected_ref(name, ref)
473
if refname != b'HEAD' and refname in self.get_refs_container():
474
raise AlreadyBranchError(self.user_url)
475
ref_chain, unused_sha = self.get_refs_container().follow(
476
self._get_selected_ref(name))
477
if ref_chain and ref_chain[0] == b'HEAD':
478
refname = ref_chain[1]
479
repo = self.open_repository()
480
return RemoteGitBranch(self, repo, refname)
482
def destroy_branch(self, name=None):
483
refname = self._get_selected_ref(name)
485
def get_changed_refs(old_refs):
487
if refname not in old_refs:
488
raise NotBranchError(self.user_url)
489
ret[refname] = dulwich.client.ZERO_SHA
492
def generate_pack_data(have, want, ofs_delta=False):
493
return pack_objects_to_data([])
494
self.send_pack(get_changed_refs, generate_pack_data)
498
return self.control_url
501
def user_transport(self):
502
return self.root_transport
505
def control_url(self):
506
return self.control_transport.base
509
def control_transport(self):
510
return self.root_transport
98
512
def open_repository(self):
99
return RemoteGitRepository(self, self._lockfiles)
101
def open_branch(self, _unsupported=False):
513
return RemoteGitRepository(self)
515
def get_branch_reference(self, name=None):
516
ref = branch_name_to_ref(name)
517
val = self.get_refs_container().read_ref(ref)
518
if val.startswith(SYMREF):
519
return val[len(SYMREF):]
522
def open_branch(self, name=None, unsupported=False,
523
ignore_fallbacks=False, ref=None, possible_transports=None,
102
525
repo = self.open_repository()
103
# TODO: Support for multiple branches in one bzrdir in bzrlib!
104
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
526
ref = self._get_selected_ref(name, ref)
528
if not nascent_ok and ref not in self.get_refs_container():
529
raise NotBranchError(
530
self.root_transport.base, controldir=self)
531
except NotGitRepository:
532
raise NotBranchError(self.root_transport.base,
534
ref_chain, unused_sha = self.get_refs_container().follow(ref)
535
return RemoteGitBranch(self, repo, ref_chain[-1])
106
def open_workingtree(self):
537
def open_workingtree(self, recommend_upgrade=False):
107
538
raise NotLocalUrl(self.transport.base)
540
def has_workingtree(self):
543
def get_peeled(self, name):
544
return self.get_refs_container().get_peeled(name)
546
def get_refs_container(self):
547
if self._refs is not None:
549
result = self.fetch_pack(lambda x: None, None,
551
lambda x: trace.mutter("git: %s" % x))
552
self._refs = remote_refs_dict_to_container(
553
result.refs, result.symrefs)
556
def push_branch(self, source, revision_id=None, overwrite=False,
557
remember=False, create_prefix=False, lossy=False,
558
name=None, tag_selector=None):
559
"""Push the source branch into this ControlDir."""
560
if revision_id is None:
561
# No revision supplied by the user, default to the branch
563
revision_id = source.last_revision()
565
push_result = GitPushResult()
566
push_result.workingtree_updated = None
567
push_result.master_branch = None
568
push_result.source_branch = source
569
push_result.stacked_on = None
570
push_result.branch_push_result = None
571
repo = self.find_repository()
572
refname = self._get_selected_ref(name)
573
ref_chain, old_sha = self.get_refs_container().follow(refname)
575
actual_refname = ref_chain[-1]
577
actual_refname = refname
578
if isinstance(source, GitBranch) and lossy:
579
raise errors.LossyPushToSameVCS(source.controldir, self)
580
source_store = get_object_store(source.repository)
581
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
582
def get_changed_refs(remote_refs):
583
if self._refs is not None:
584
update_refs_container(self._refs, remote_refs)
586
# TODO(jelmer): Unpeel if necessary
587
push_result.new_original_revid = revision_id
589
new_sha = source_store._lookup_revision_sha1(revision_id)
592
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
593
except errors.NoSuchRevision:
594
raise errors.NoRoundtrippingSupport(
595
source, self.open_branch(name=name, nascent_ok=True))
597
if remote_divergence(old_sha, new_sha, source_store):
598
raise DivergedBranches(
599
source, self.open_branch(name, nascent_ok=True))
600
ret[actual_refname] = new_sha
602
for tagname, revid in viewitems(source.tags.get_tag_dict()):
603
if tag_selector and not tag_selector(tagname):
607
new_sha = source_store._lookup_revision_sha1(revid)
609
if source.repository.has_revision(revid):
613
new_sha = repo.lookup_bzr_revision_id(revid)[0]
614
except errors.NoSuchRevision:
616
ret[tag_name_to_ref(tagname)] = new_sha
618
with source_store.lock_read():
620
generate_pack_data = source_store.generate_lossy_pack_data
622
generate_pack_data = source_store.generate_pack_data
623
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
624
push_result.new_revid = repo.lookup_foreign_revision_id(
625
new_refs[actual_refname])
626
if old_sha is not None:
627
push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
629
push_result.old_revid = NULL_REVISION
630
if self._refs is not None:
631
update_refs_container(self._refs, new_refs)
632
push_result.target_branch = self.open_branch(name)
633
if old_sha is not None:
634
push_result.branch_push_result = GitBranchPushResult()
635
push_result.branch_push_result.source_branch = source
636
push_result.branch_push_result.target_branch = (
637
push_result.target_branch)
638
push_result.branch_push_result.local_branch = None
639
push_result.branch_push_result.master_branch = (
640
push_result.target_branch)
641
push_result.branch_push_result.old_revid = push_result.old_revid
642
push_result.branch_push_result.new_revid = push_result.new_revid
643
push_result.branch_push_result.new_original_revid = (
644
push_result.new_original_revid)
645
if source.get_push_location() is None or remember:
646
source.set_push_location(push_result.target_branch.base)
649
def _find_commondir(self):
650
# There is no way to find the commondir, if there is any.
110
654
class EmptyObjectStoreIterator(dict):
116
660
class TemporaryPackIterator(Pack):
118
662
def __init__(self, path, resolve_ext_ref):
119
self.resolve_ext_ref = resolve_ext_ref
120
super(TemporaryPackIterator, self).__init__(path)
663
super(TemporaryPackIterator, self).__init__(
664
path, resolve_ext_ref=resolve_ext_ref)
665
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)
667
def _idx_load_or_generate(self, path):
668
if not os.path.exists(path):
669
with ui.ui_factory.nested_progress_bar() as pb:
670
def report_progress(cur, total):
671
pb.update("generating index", cur, total)
672
self.data.create_index(path, progress=report_progress)
673
return load_pack_index(path)
129
675
def __del__(self):
130
os.remove(self._data_path)
131
os.remove(self._idx_path)
676
if self._idx is not None:
678
os.remove(self._idx_path)
679
if self._data is not None:
681
os.remove(self._data_path)
684
class BzrGitHttpClient(dulwich.client.HttpGitClient):
686
def __init__(self, transport, *args, **kwargs):
687
self.transport = transport
688
url = urlutils.URL.from_string(transport.external_url())
689
url.user = url.quoted_user = None
690
url.password = url.quoted_password = None
691
url = urlutils.strip_segment_parameters(str(url))
692
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
694
def _http_request(self, url, headers=None, data=None,
695
allow_compression=False):
696
"""Perform HTTP request.
698
:param url: Request URL.
699
:param headers: Optional custom headers to override defaults.
700
:param data: Request data.
701
:param allow_compression: Allow GZipped communication.
702
:return: Tuple (`response`, `read`), where response is an `urllib3`
703
response object with additional `content_type` and
704
`redirect_location` properties, and `read` is a consumable read
705
method for the response data.
707
if is_github_url(url):
708
headers['User-agent'] = user_agent_for_github()
709
headers["Pragma"] = "no-cache"
710
if allow_compression:
711
headers["Accept-Encoding"] = "gzip"
713
headers["Accept-Encoding"] = "identity"
715
response = self.transport.request(
716
('GET' if data is None else 'POST'),
719
headers=headers, retries=8)
721
if response.status == 404:
722
raise NotGitRepository()
723
elif response.status != 200:
724
raise GitProtocolError("unexpected http resp %d for %s" %
725
(response.status, url))
727
# TODO: Optimization available by adding `preload_content=False` to the
728
# request and just passing the `read` method on instead of going via
729
# `BytesIO`, if we can guarantee that the entire response is consumed
730
# before issuing the next to still allow for connection reuse from the
732
if response.getheader("Content-Encoding") == "gzip":
733
read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
737
class WrapResponse(object):
739
def __init__(self, response):
740
self._response = response
741
self.status = response.status
742
self.content_type = response.getheader("Content-Type")
743
self.redirect_location = response._actual.geturl()
746
return self._response.readlines()
751
return WrapResponse(response), read
754
def _git_url_and_path_from_transport(external_url):
755
url = urlutils.strip_segment_parameters(external_url)
756
return urlparse.urlsplit(url)
759
class RemoteGitControlDirFormat(GitControlDirFormat):
760
"""The .git directory control format."""
762
supports_workingtrees = False
765
def _known_formats(self):
766
return set([RemoteGitControlDirFormat()])
768
def get_branch_format(self):
769
return RemoteGitBranchFormat()
772
def repository_format(self):
773
return GitRepositoryFormat()
775
def is_initializable(self):
778
def is_supported(self):
781
def open(self, transport, _found=None):
782
"""Open this directory.
785
split_url = _git_url_and_path_from_transport(transport.external_url())
786
if isinstance(transport, GitSmartTransport):
787
client = transport._get_client()
788
elif split_url.scheme in ("http", "https"):
789
client = BzrGitHttpClient(transport)
790
elif split_url.scheme in ('file', ):
791
client = dulwich.client.LocalGitClient()
793
raise NotBranchError(transport.base)
795
pass # TODO(jelmer): Actually probe for something
796
return RemoteGitDir(transport, self, client, split_url.path)
798
def get_format_description(self):
799
return "Remote Git Repository"
801
def initialize_on_transport(self, transport):
802
raise UninitializableFormat(self)
804
def supports_transport(self, transport):
806
external_url = transport.external_url()
807
except InProcessTransport:
808
raise NotBranchError(path=transport.base)
809
return (external_url.startswith("http:")
810
or external_url.startswith("https:")
811
or external_url.startswith("git+")
812
or external_url.startswith("git:"))
815
class GitRemoteRevisionTree(RevisionTree):
817
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
818
"""Create an archive of this tree.
820
:param format: Format name (e.g. 'tar')
821
:param name: target file name
822
:param root: Root directory name (or None)
823
:param subdir: Subdirectory to export (or None)
824
:return: Iterator over archive chunks
826
commit = self._repository.lookup_bzr_revision_id(
827
self.get_revision_id())[0]
828
f = tempfile.SpooledTemporaryFile()
829
# git-upload-archive(1) generaly only supports refs. So let's see if we
833
self._repository.controldir.get_refs_container().as_dict().items()}
835
committish = reverse_refs[commit]
837
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
838
# Let's hope for the best.
840
self._repository.archive(
841
format, committish, f.write,
842
subdirs=([subdir] if subdir else None),
843
prefix=(root + '/') if root else '')
845
return osutils.file_iterator(f)
847
def is_versioned(self, path):
848
raise GitSmartRemoteNotSupported(self.is_versioned, self)
850
def has_filename(self, path):
851
raise GitSmartRemoteNotSupported(self.has_filename, self)
853
def get_file_text(self, path):
854
raise GitSmartRemoteNotSupported(self.get_file_text, self)
856
def list_files(self, include_root=False, from_dir=None, recursive=True):
857
raise GitSmartRemoteNotSupported(self.list_files, self)
134
860
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,
862
supports_random_access = False
866
return self.control_url
868
def get_parent_map(self, revids):
869
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
871
def archive(self, *args, **kwargs):
872
return self.controldir.archive(*args, **kwargs)
874
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):
876
return self.controldir.fetch_pack(
877
determine_wants, graph_walker, pack_data, progress)
879
def send_pack(self, get_changed_refs, generate_pack_data):
880
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
882
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
145
884
fd, path = tempfile.mkstemp(suffix=".pack")
146
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
886
self.fetch_pack(determine_wants, graph_walker,
887
lambda x: os.write(fd, x), progress)
148
890
if os.path.getsize(path) == 0:
149
891
return EmptyObjectStoreIterator()
150
892
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
894
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
895
# This won't work for any round-tripped bzr revisions, but it's a
898
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
899
except InvalidRevisionId:
900
raise NoSuchRevision(self, bzr_revid)
902
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
903
"""Lookup a revision id.
907
mapping = self.get_mapping()
908
# Not really an easy way to parse foreign revids here..
909
return mapping.revision_id_foreign_to_bzr(foreign_revid)
911
def revision_tree(self, revid):
912
return GitRemoteRevisionTree(self, revid)
914
def get_revisions(self, revids):
915
raise GitSmartRemoteNotSupported(self.get_revisions, self)
917
def has_revisions(self, revids):
918
raise GitSmartRemoteNotSupported(self.get_revisions, self)
921
class RemoteGitTagDict(GitTags):
923
def set_tag(self, name, revid):
924
sha = self.branch.lookup_bzr_revision_id(revid)[0]
925
self._set_ref(name, sha)
927
def delete_tag(self, name):
928
self._set_ref(name, dulwich.client.ZERO_SHA)
930
def _set_ref(self, name, sha):
931
ref = tag_name_to_ref(name)
933
def get_changed_refs(old_refs):
935
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
936
raise NoSuchTag(name)
940
def generate_pack_data(have, want, ofs_delta=False):
941
return pack_objects_to_data([])
942
self.repository.send_pack(get_changed_refs, generate_pack_data)
153
945
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)
947
def __init__(self, controldir, repository, name):
949
super(RemoteGitBranch, self).__init__(controldir, repository, name,
950
RemoteGitBranchFormat())
952
def last_revision_info(self):
953
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
957
return self.control_url
960
def control_url(self):
963
def revision_id_to_revno(self, revision_id):
964
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
164
966
def last_revision(self):
165
return self.mapping.revision_id_foreign_to_bzr(self._ref)
967
return self.lookup_foreign_revision_id(self.head)
971
if self._sha is not None:
973
refs = self.controldir.get_refs_container()
974
name = branch_name_to_ref(self.name)
976
self._sha = refs[name]
978
raise NoSuchRef(name, self.repository.user_url, refs)
167
981
def _synchronize_history(self, destination, revision_id):
168
982
"""See Branch._synchronize_history()."""
169
destination.generate_revision_history(self.last_revision())
983
if revision_id is None:
984
revision_id = self.last_revision()
985
destination.generate_revision_history(revision_id)
987
def _get_parent_location(self):
990
def get_push_location(self):
993
def set_push_location(self, url):
996
def _iter_tag_refs(self):
997
"""Iterate over the tag refs.
999
:param refs: Refs dictionary (name -> git sha1)
1000
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
1002
refs = self.controldir.get_refs_container()
1003
for ref_name, unpeeled in refs.as_dict().items():
1005
tag_name = ref_to_tag_name(ref_name)
1006
except (ValueError, UnicodeDecodeError):
1008
peeled = refs.get_peeled(ref_name)
1010
# Let's just hope it's a commit
1012
if not isinstance(tag_name, text_type):
1013
raise TypeError(tag_name)
1014
yield (ref_name, tag_name, peeled, unpeeled)
1016
def set_last_revision_info(self, revno, revid):
1017
self.generate_revision_history(revid)
1019
def generate_revision_history(self, revision_id, last_rev=None,
1021
sha = self.lookup_bzr_revision_id(revision_id)[0]
1022
def get_changed_refs(old_refs):
1023
return {self.ref: sha}
1024
def generate_pack_data(have, want, ofs_delta=False):
1025
return pack_objects_to_data([])
1026
self.repository.send_pack(get_changed_refs, generate_pack_data)
1030
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1033
for k, v in refs_dict.items():
1038
for name, target in symrefs_dict.items():
1039
base[name] = SYMREF + target
1040
ret = DictRefsContainer(base)
1041
ret._peeled = peeled
1045
def update_refs_container(container, refs_dict):
1048
for k, v in refs_dict.items():
1053
container._peeled = peeled
1054
container._refs.update(base)