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."""
20
from io import BytesIO
35
from ..errors import (
47
UninitializableFormat,
49
from ..revision import NULL_REVISION
50
from ..revisiontree import RevisionTree
51
from ..transport import (
53
register_urlparse_netloc_protocol,
59
user_agent_for_github,
27
61
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
75
GitSmartRemoteNotSupported,
78
from .mapping import (
81
from .object_store import (
87
from .repository import (
100
from dulwich.errors import (
104
from dulwich.pack import (
106
pack_objects_to_data,
108
from dulwich.protocol import ZERO_SHA
109
from dulwich.refs import (
113
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
120
import urllib.parse as urlparse
121
from urllib.parse import splituser
123
# urlparse only supports a limited number of schemes by default
124
register_urlparse_netloc_protocol('git')
125
register_urlparse_netloc_protocol('git+ssh')
127
from dulwich.pack import load_pack_index
130
class GitPushResult(PushResult):
132
def _lookup_revno(self, revid):
134
return _quick_lookup_revno(self.source_branch, self.target_branch,
136
except GitSmartRemoteNotSupported:
141
return self._lookup_revno(self.old_revid)
145
return self._lookup_revno(self.new_revid)
148
# Don't run any tests on GitSmartTransport as it is not intended to be
44
149
# a full implementation of Transport
45
150
def get_test_permutations():
154
def split_git_url(url):
158
:return: Tuple with host, port, username, path.
160
parsed_url = urlparse.urlparse(url)
161
path = urlparse.unquote(parsed_url.path)
162
if path.startswith("/~"):
164
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
167
class RemoteGitError(BzrError):
169
_fmt = "Remote server error: %(msg)s"
172
class HeadUpdateFailed(BzrError):
174
_fmt = ("Unable to update remote HEAD branch. To update the master "
175
"branch, specify the URL %(base_url)s,branch=master.")
177
def __init__(self, base_url):
178
super(HeadUpdateFailed, self).__init__()
179
self.base_url = base_url
182
def parse_git_error(url, message):
183
"""Parse a remote git server error and return a bzr exception.
185
:param url: URL of the remote repository
186
:param message: Message sent by the remote git server
188
message = str(message).strip()
189
if (message.startswith("Could not find Repository ")
190
or message == 'Repository not found.'
191
or (message.startswith('Repository ') and
192
message.endswith(' not found.'))):
193
return NotBranchError(url, message)
194
if message == "HEAD failed to update":
195
base_url = urlutils.strip_segment_parameters(url)
196
return HeadUpdateFailed(base_url)
197
if message.startswith('access denied or repository not exported:'):
198
extra, path = message.split(':', 1)
199
return PermissionDenied(path.strip(), extra)
200
if message.endswith('You are not allowed to push code to this project.'):
201
return PermissionDenied(url, message)
202
if message.endswith(' does not appear to be a git repository'):
203
return NotBranchError(url, message)
204
if re.match('(.+) is not a valid repository name',
205
message.splitlines()[0]):
206
return NotBranchError(url, message)
207
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
209
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
210
# Don't know, just return it to the user as-is
211
return RemoteGitError(message)
49
214
class GitSmartTransport(Transport):
51
216
def __init__(self, url, _client=None):
52
217
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)
218
(self._host, self._port, self._username, self._path) = \
220
if 'transport' in debug.debug_flags:
221
trace.mutter('host: %r, user: %r, port: %r, path: %r',
222
self._host, self._username, self._port, self._path)
57
223
self._client = _client
224
self._stripped_path = self._path.rsplit(",", 1)[0]
226
def external_url(self):
229
def has(self, relpath):
59
232
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"])
233
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)
236
return self._stripped_path
74
238
def get(self, path):
75
239
raise NoSuchFile(path)
85
249
newurl = urlutils.join(self.base, offset)
87
return GitSmartTransport(newurl, self._client)
251
return self.__class__(newurl, self._client)
254
class TCPGitSmartTransport(GitSmartTransport):
258
def _get_client(self):
259
if self._client is not None:
264
# return dulwich.client.LocalGitClient()
265
return dulwich.client.SubprocessGitClient()
266
return dulwich.client.TCPGitClient(
267
self._host, self._port, report_activity=self._report_activity)
270
class SSHSocketWrapper(object):
272
def __init__(self, sock):
275
def read(self, len=None):
276
return self.sock.recv(len)
278
def write(self, data):
279
return self.sock.write(data)
282
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
285
class DulwichSSHVendor(dulwich.client.SSHVendor):
288
from ..transport import ssh
289
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
291
def run_command(self, host, command, username=None, port=None):
292
connection = self.bzr_ssh_vendor.connect_ssh(
293
username=username, password=None, port=port, host=host,
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(
322
self._host, self._port, self._username,
323
report_activity=self._report_activity)
324
# Set up alternate pack program paths
325
upload_pack = location_config.get_user_option('git_upload_pack')
327
client.alternative_paths["upload-pack"] = upload_pack
328
receive_pack = location_config.get_user_option('git_receive_pack')
330
client.alternative_paths["receive-pack"] = receive_pack
334
class RemoteGitBranchFormat(GitBranchFormat):
336
def get_format_description(self):
337
return 'Remote Git Branch'
340
def _matchingcontroldir(self):
341
return RemoteGitControlDirFormat()
343
def initialize(self, a_controldir, name=None, repository=None,
344
append_revisions_only=None):
345
raise UninitializableFormat(self)
348
class DefaultProgressReporter(object):
350
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
351
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
353
def __init__(self, pb):
356
def progress(self, text):
357
text = text.rstrip(b"\r\n")
358
text = text.decode('utf-8')
359
if text.lower().startswith('error: '):
360
trace.show_error('git: %s', text[len(b'error: '):])
362
trace.mutter("git: %s", text)
363
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
365
(text, pct, current, total) = g.groups()
366
self.pb.update(text, int(current), int(total))
368
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
370
(text, total) = g.groups()
371
self.pb.update(text, None, int(total))
373
trace.note("%s", text)
90
376
class RemoteGitDir(GitDir):
92
def __init__(self, transport, lockfiles, format):
378
def __init__(self, transport, format, client, client_path):
93
379
self._format = format
94
380
self.root_transport = transport
95
381
self.transport = transport
96
self._lockfiles = lockfiles
382
self._mode_check_done = None
383
self._client = client
384
self._client_path = client_path
385
self.base = self.root_transport.base
389
def _gitrepository_class(self):
390
return RemoteGitRepository
392
def archive(self, format, committish, write_data, progress=None,
393
write_error=None, subdirs=None, prefix=None):
395
pb = ui.ui_factory.nested_progress_bar()
396
progress = DefaultProgressReporter(pb).progress
399
def progress_wrapper(message):
400
if message.startswith(b"fatal: Unknown archive format \'"):
401
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
402
raise errors.NoSuchExportFormat(format.decode('ascii'))
403
return progress(message)
405
self._client.archive(
406
self._client_path, committish, write_data, progress_wrapper,
408
format=(format.encode('ascii') if format else None),
410
prefix=(prefix.encode('utf-8') if prefix else None))
411
except GitProtocolError as e:
412
raise parse_git_error(self.transport.external_url(), e)
417
def fetch_pack(self, determine_wants, graph_walker, pack_data,
420
pb = ui.ui_factory.nested_progress_bar()
421
progress = DefaultProgressReporter(pb).progress
425
result = self._client.fetch_pack(
426
self._client_path, determine_wants, graph_walker, pack_data,
428
if result.refs is None:
430
self._refs = remote_refs_dict_to_container(
431
result.refs, result.symrefs)
433
except GitProtocolError as e:
434
raise parse_git_error(self.transport.external_url(), e)
439
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
441
pb = ui.ui_factory.nested_progress_bar()
442
progress = DefaultProgressReporter(pb).progress
446
def get_changed_refs_wrapper(remote_refs):
447
if self._refs is not None:
448
update_refs_container(self._refs, remote_refs)
449
return get_changed_refs(remote_refs)
451
return self._client.send_pack(
452
self._client_path, get_changed_refs_wrapper,
453
generate_pack_data, progress)
454
except GitProtocolError as e:
455
raise parse_git_error(self.transport.external_url(), e)
460
def create_branch(self, name=None, repository=None,
461
append_revisions_only=None, ref=None):
462
refname = self._get_selected_ref(name, ref)
463
if refname != b'HEAD' and refname in self.get_refs_container():
464
raise AlreadyBranchError(self.user_url)
465
ref_chain, unused_sha = self.get_refs_container().follow(
466
self._get_selected_ref(name))
467
if ref_chain and ref_chain[0] == b'HEAD':
468
refname = ref_chain[1]
469
repo = self.open_repository()
470
return RemoteGitBranch(self, repo, refname)
472
def destroy_branch(self, name=None):
473
refname = self._get_selected_ref(name)
475
def get_changed_refs(old_refs):
477
if refname not in old_refs:
478
raise NotBranchError(self.user_url)
479
ret[refname] = dulwich.client.ZERO_SHA
482
def generate_pack_data(have, want, ofs_delta=False):
483
return pack_objects_to_data([])
484
self.send_pack(get_changed_refs, generate_pack_data)
488
return self.control_url
491
def user_transport(self):
492
return self.root_transport
495
def control_url(self):
496
return self.control_transport.base
499
def control_transport(self):
500
return self.root_transport
98
502
def open_repository(self):
99
return RemoteGitRepository(self, self._lockfiles)
101
def open_branch(self, _unsupported=False):
503
return RemoteGitRepository(self)
505
def get_branch_reference(self, name=None):
506
ref = branch_name_to_ref(name)
507
val = self.get_refs_container().read_ref(ref)
508
if val.startswith(SYMREF):
509
return val[len(SYMREF):]
512
def open_branch(self, name=None, unsupported=False,
513
ignore_fallbacks=False, ref=None, possible_transports=None,
102
515
repo = self.open_repository()
103
# TODO: Support for multiple branches in one bzrdir in bzrlib!
104
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
516
ref = self._get_selected_ref(name, ref)
518
if not nascent_ok and ref not in self.get_refs_container():
519
raise NotBranchError(
520
self.root_transport.base, controldir=self)
521
except NotGitRepository:
522
raise NotBranchError(self.root_transport.base,
524
ref_chain, unused_sha = self.get_refs_container().follow(ref)
525
return RemoteGitBranch(self, repo, ref_chain[-1])
106
def open_workingtree(self):
527
def open_workingtree(self, recommend_upgrade=False):
107
528
raise NotLocalUrl(self.transport.base)
530
def has_workingtree(self):
533
def get_peeled(self, name):
534
return self.get_refs_container().get_peeled(name)
536
def get_refs_container(self):
537
if self._refs is not None:
539
result = self.fetch_pack(lambda x: None, None,
541
lambda x: trace.mutter("git: %s" % x))
542
self._refs = remote_refs_dict_to_container(
543
result.refs, result.symrefs)
546
def push_branch(self, source, revision_id=None, overwrite=False,
547
remember=False, create_prefix=False, lossy=False,
549
"""Push the source branch into this ControlDir."""
550
if revision_id is None:
551
# No revision supplied by the user, default to the branch
553
revision_id = source.last_revision()
555
push_result = GitPushResult()
556
push_result.workingtree_updated = None
557
push_result.master_branch = None
558
push_result.source_branch = source
559
push_result.stacked_on = None
560
push_result.branch_push_result = None
561
repo = self.find_repository()
562
refname = self._get_selected_ref(name)
563
ref_chain, old_sha = self.get_refs_container().follow(refname)
565
actual_refname = ref_chain[-1]
567
actual_refname = refname
568
if isinstance(source, GitBranch) and lossy:
569
raise errors.LossyPushToSameVCS(source.controldir, self)
570
source_store = get_object_store(source.repository)
571
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
572
def get_changed_refs(remote_refs):
573
if self._refs is not None:
574
update_refs_container(self._refs, remote_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(old_sha, new_sha, source_store):
588
raise DivergedBranches(
589
source, self.open_branch(name, nascent_ok=True))
590
ret[actual_refname] = new_sha
592
for tagname, revid in source.tags.get_tag_dict().items():
595
new_sha = source_store._lookup_revision_sha1(revid)
597
if source.repository.has_revision(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(
613
new_refs[actual_refname])
614
if old_sha is not None:
615
push_result.old_revid = repo.lookup_foreign_revision_id(old_sha)
617
push_result.old_revid = NULL_REVISION
618
if self._refs is not None:
619
update_refs_container(self._refs, new_refs)
620
push_result.target_branch = self.open_branch(name)
621
if old_sha is not None:
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.
110
642
class EmptyObjectStoreIterator(dict):
116
648
class TemporaryPackIterator(Pack):
118
650
def __init__(self, path, resolve_ext_ref):
119
self.resolve_ext_ref = resolve_ext_ref
120
super(TemporaryPackIterator, self).__init__(path)
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)
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)
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)
129
663
def __del__(self):
130
os.remove(self._data_path)
131
os.remove(self._idx_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=BytesIO(response.read())).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)
134
848
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,
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,
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):
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,
145
872
fd, path = tempfile.mkstemp(suffix=".pack")
146
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
874
self.fetch_pack(determine_wants, graph_walker,
875
lambda x: os.write(fd, x), progress)
148
878
if os.path.getsize(path) == 0:
149
879
return EmptyObjectStoreIterator()
150
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)
153
933
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)
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)
164
954
def last_revision(self):
165
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)
167
969
def _synchronize_history(self, destination, revision_id):
168
970
"""See Branch._synchronize_history()."""
169
destination.generate_revision_history(self.last_revision())
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, str):
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
1033
def update_refs_container(container, refs_dict):
1036
for k, v in refs_dict.items():
1041
container._peeled = peeled
1042
container._refs.update(base)