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 ..revisiontree import RevisionTree
52
from ..transport import (
54
register_urlparse_netloc_protocol,
60
user_agent_for_github,
27
62
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
76
GitSmartRemoteNotSupported,
79
from .mapping import (
82
from .object_store import (
88
from .repository import (
100
import dulwich.client
101
from dulwich.errors import (
105
from dulwich.pack import (
107
pack_objects_to_data,
109
from dulwich.protocol import ZERO_SHA
110
from dulwich.refs import (
114
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
121
import urllib.parse as urlparse
122
from urllib.parse import splituser
124
# urlparse only supports a limited number of schemes by default
125
register_urlparse_netloc_protocol('git')
126
register_urlparse_netloc_protocol('git+ssh')
128
from dulwich.pack import load_pack_index
131
class GitPushResult(PushResult):
133
def _lookup_revno(self, revid):
135
return _quick_lookup_revno(self.source_branch, self.target_branch,
137
except GitSmartRemoteNotSupported:
142
return self._lookup_revno(self.old_revid)
146
return self._lookup_revno(self.new_revid)
149
# Don't run any tests on GitSmartTransport as it is not intended to be
44
150
# a full implementation of Transport
45
151
def get_test_permutations():
155
def split_git_url(url):
159
:return: Tuple with host, port, username, path.
161
parsed_url = urlparse.urlparse(url)
162
path = urlparse.unquote(parsed_url.path)
163
if path.startswith("/~"):
165
return ((parsed_url.hostname or '', parsed_url.port, parsed_url.username, path))
168
class RemoteGitError(BzrError):
170
_fmt = "Remote server error: %(msg)s"
173
class HeadUpdateFailed(BzrError):
175
_fmt = ("Unable to update remote HEAD branch. To update the master "
176
"branch, specify the URL %(base_url)s,branch=master.")
178
def __init__(self, base_url):
179
super(HeadUpdateFailed, self).__init__()
180
self.base_url = base_url
183
def parse_git_error(url, message):
184
"""Parse a remote git server error and return a bzr exception.
186
:param url: URL of the remote repository
187
:param message: Message sent by the remote git server
189
message = str(message).strip()
190
if (message.startswith("Could not find Repository ")
191
or message == 'Repository not found.'
192
or (message.startswith('Repository ') and
193
message.endswith(' not found.'))):
194
return NotBranchError(url, message)
195
if message == "HEAD failed to update":
196
base_url = urlutils.strip_segment_parameters(url)
197
return HeadUpdateFailed(base_url)
198
if message.startswith('access denied or repository not exported:'):
199
extra, path = message.split(':', 1)
200
return PermissionDenied(path.strip(), extra)
201
if message.endswith('You are not allowed to push code to this project.'):
202
return PermissionDenied(url, message)
203
if message.endswith(' does not appear to be a git repository'):
204
return NotBranchError(url, message)
205
if re.match('(.+) is not a valid repository name',
206
message.splitlines()[0]):
207
return NotBranchError(url, message)
208
m = re.match(r'Permission to ([^ ]+) denied to ([^ ]+)\.', message)
210
return PermissionDenied(m.group(1), 'denied to %s' % m.group(2))
211
# Don't know, just return it to the user as-is
212
return RemoteGitError(message)
49
215
class GitSmartTransport(Transport):
51
217
def __init__(self, url, _client=None):
52
218
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)
219
(self._host, self._port, self._username, self._path) = \
221
if 'transport' in debug.debug_flags:
222
trace.mutter('host: %r, user: %r, port: %r, path: %r',
223
self._host, self._username, self._port, self._path)
57
224
self._client = _client
225
self._stripped_path = self._path.rsplit(",", 1)[0]
227
def external_url(self):
230
def has(self, relpath):
59
233
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"])
234
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)
237
return self._stripped_path
74
239
def get(self, path):
75
240
raise NoSuchFile(path)
85
250
newurl = urlutils.join(self.base, offset)
87
return GitSmartTransport(newurl, self._client)
252
return self.__class__(newurl, self._client)
255
class TCPGitSmartTransport(GitSmartTransport):
259
def _get_client(self):
260
if self._client is not None:
265
# return dulwich.client.LocalGitClient()
266
return dulwich.client.SubprocessGitClient()
267
return dulwich.client.TCPGitClient(
268
self._host, self._port, report_activity=self._report_activity)
271
class SSHSocketWrapper(object):
273
def __init__(self, sock):
276
def read(self, len=None):
277
return self.sock.recv(len)
279
def write(self, data):
280
return self.sock.write(data)
283
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
286
class DulwichSSHVendor(dulwich.client.SSHVendor):
289
from ..transport import ssh
290
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
292
def run_command(self, host, command, username=None, port=None):
293
connection = self.bzr_ssh_vendor.connect_ssh(
294
username=username, password=None, port=port, host=host,
296
(kind, io_object) = connection.get_sock_or_pipes()
298
return SSHSocketWrapper(io_object)
300
raise AssertionError("Unknown io object kind %r'" % kind)
303
# dulwich.client.get_ssh_vendor = DulwichSSHVendor
306
class SSHGitSmartTransport(GitSmartTransport):
311
path = self._stripped_path
312
if path.startswith("/~/"):
316
def _get_client(self):
317
if self._client is not None:
321
location_config = config.LocationConfig(self.base)
322
client = dulwich.client.SSHGitClient(
323
self._host, self._port, self._username,
324
report_activity=self._report_activity)
325
# Set up alternate pack program paths
326
upload_pack = location_config.get_user_option('git_upload_pack')
328
client.alternative_paths["upload-pack"] = upload_pack
329
receive_pack = location_config.get_user_option('git_receive_pack')
331
client.alternative_paths["receive-pack"] = receive_pack
335
class RemoteGitBranchFormat(GitBranchFormat):
337
def get_format_description(self):
338
return 'Remote Git Branch'
341
def _matchingcontroldir(self):
342
return RemoteGitControlDirFormat()
344
def initialize(self, a_controldir, name=None, repository=None,
345
append_revisions_only=None):
346
raise UninitializableFormat(self)
349
class DefaultProgressReporter(object):
351
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
352
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
354
def __init__(self, pb):
357
def progress(self, text):
358
text = text.rstrip(b"\r\n")
359
text = text.decode('utf-8')
360
if text.lower().startswith('error: '):
361
trace.show_error('git: %s', text[len(b'error: '):])
363
trace.mutter("git: %s", text)
364
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
366
(text, pct, current, total) = g.groups()
367
self.pb.update(text, int(current), int(total))
369
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
371
(text, total) = g.groups()
372
self.pb.update(text, None, int(total))
374
trace.note("%s", text)
90
377
class RemoteGitDir(GitDir):
92
def __init__(self, transport, lockfiles, format):
379
def __init__(self, transport, format, client, client_path):
93
380
self._format = format
94
381
self.root_transport = transport
95
382
self.transport = transport
96
self._lockfiles = lockfiles
383
self._mode_check_done = None
384
self._client = client
385
self._client_path = client_path
386
self.base = self.root_transport.base
390
def _gitrepository_class(self):
391
return RemoteGitRepository
393
def archive(self, format, committish, write_data, progress=None,
394
write_error=None, subdirs=None, prefix=None):
396
pb = ui.ui_factory.nested_progress_bar()
397
progress = DefaultProgressReporter(pb).progress
400
def progress_wrapper(message):
401
if message.startswith(b"fatal: Unknown archive format \'"):
402
format = message.strip()[len(b"fatal: Unknown archive format '"):-1]
403
raise errors.NoSuchExportFormat(format.decode('ascii'))
404
return progress(message)
406
self._client.archive(
407
self._client_path, committish, write_data, progress_wrapper,
409
format=(format.encode('ascii') if format else None),
411
prefix=(prefix.encode('utf-8') if prefix else None))
412
except GitProtocolError as e:
413
raise parse_git_error(self.transport.external_url(), e)
418
def fetch_pack(self, determine_wants, graph_walker, pack_data,
421
pb = ui.ui_factory.nested_progress_bar()
422
progress = DefaultProgressReporter(pb).progress
426
result = self._client.fetch_pack(
427
self._client_path, determine_wants, graph_walker, pack_data,
429
if result.refs is None:
431
self._refs = remote_refs_dict_to_container(
432
result.refs, result.symrefs)
434
except GitProtocolError as e:
435
raise parse_git_error(self.transport.external_url(), e)
440
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
442
pb = ui.ui_factory.nested_progress_bar()
443
progress = DefaultProgressReporter(pb).progress
447
def get_changed_refs_wrapper(refs):
448
# TODO(jelmer): This drops symref information
449
self._refs = remote_refs_dict_to_container(refs)
450
return get_changed_refs(refs)
452
return self._client.send_pack(
453
self._client_path, get_changed_refs_wrapper,
454
generate_pack_data, progress)
455
except GitProtocolError as e:
456
raise parse_git_error(self.transport.external_url(), e)
461
def create_branch(self, name=None, repository=None,
462
append_revisions_only=None, ref=None):
463
refname = self._get_selected_ref(name, ref)
464
if refname != b'HEAD' and refname in self.get_refs_container():
465
raise AlreadyBranchError(self.user_url)
466
if refname in self.get_refs_container():
467
ref_chain, unused_sha = self.get_refs_container().follow(
468
self._get_selected_ref(None))
469
if ref_chain[0] == b'HEAD':
470
refname = ref_chain[1]
471
repo = self.open_repository()
472
return RemoteGitBranch(self, repo, refname)
474
def destroy_branch(self, name=None):
475
refname = self._get_selected_ref(name)
477
def get_changed_refs(old_refs):
479
if refname not in old_refs:
480
raise NotBranchError(self.user_url)
481
ret[refname] = dulwich.client.ZERO_SHA
484
def generate_pack_data(have, want, ofs_delta=False):
485
return pack_objects_to_data([])
486
self.send_pack(get_changed_refs, generate_pack_data)
490
return self.control_url
493
def user_transport(self):
494
return self.root_transport
497
def control_url(self):
498
return self.control_transport.base
501
def control_transport(self):
502
return self.root_transport
98
504
def open_repository(self):
99
return RemoteGitRepository(self, self._lockfiles)
101
def open_branch(self, _unsupported=False):
505
return RemoteGitRepository(self)
507
def get_branch_reference(self, name=None):
508
ref = branch_name_to_ref(name)
509
val = self.get_refs_container().read_ref(ref)
510
if val.startswith(SYMREF):
511
return val[len(SYMREF):]
514
def open_branch(self, name=None, unsupported=False,
515
ignore_fallbacks=False, ref=None, possible_transports=None,
102
517
repo = self.open_repository()
103
# TODO: Support for multiple branches in one bzrdir in bzrlib!
104
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
518
ref = self._get_selected_ref(name, ref)
520
if not nascent_ok and ref not in self.get_refs_container():
521
raise NotBranchError(
522
self.root_transport.base, controldir=self)
523
except NotGitRepository:
524
raise NotBranchError(self.root_transport.base,
526
ref_chain, unused_sha = self.get_refs_container().follow(ref)
527
return RemoteGitBranch(self, repo, ref_chain[-1])
106
def open_workingtree(self):
529
def open_workingtree(self, recommend_upgrade=False):
107
530
raise NotLocalUrl(self.transport.base)
532
def has_workingtree(self):
535
def get_peeled(self, name):
536
return self.get_refs_container().get_peeled(name)
538
def get_refs_container(self):
539
if self._refs is not None:
541
result = self.fetch_pack(lambda x: None, None,
543
lambda x: trace.mutter("git: %s" % x))
544
self._refs = remote_refs_dict_to_container(
545
result.refs, result.symrefs)
548
def push_branch(self, source, revision_id=None, overwrite=False,
549
remember=False, create_prefix=False, lossy=False,
551
"""Push the source branch into this ControlDir."""
552
if revision_id is None:
553
# No revision supplied by the user, default to the branch
555
revision_id = source.last_revision()
557
push_result = GitPushResult()
558
push_result.workingtree_updated = None
559
push_result.master_branch = None
560
push_result.source_branch = source
561
push_result.stacked_on = None
562
push_result.branch_push_result = None
563
repo = self.find_repository()
564
refname = self._get_selected_ref(name)
565
if isinstance(source, GitBranch) and lossy:
566
raise errors.LossyPushToSameVCS(source.controldir, self)
567
source_store = get_object_store(source.repository)
568
fetch_tags = source.get_config_stack().get('branch.fetch_tags')
569
def get_changed_refs(refs):
570
self._refs = remote_refs_dict_to_container(refs)
572
# TODO(jelmer): Unpeel if necessary
573
push_result.new_original_revid = revision_id
575
new_sha = source_store._lookup_revision_sha1(revision_id)
578
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
579
except errors.NoSuchRevision:
580
raise errors.NoRoundtrippingSupport(
581
source, self.open_branch(name=name, nascent_ok=True))
583
if remote_divergence(ret.get(refname), new_sha,
585
raise DivergedBranches(
586
source, self.open_branch(name, nascent_ok=True))
587
ret[refname] = new_sha
589
for tagname, revid in source.tags.get_tag_dict().items():
591
new_sha = source_store._lookup_revision_sha1(revid)
594
new_sha = repo.lookup_bzr_revision_id(revid)[0]
595
except errors.NoSuchRevision:
597
ret[tag_name_to_ref(tagname)] = new_sha
599
with source_store.lock_read():
601
generate_pack_data = source_store.generate_lossy_pack_data
603
generate_pack_data = source_store.generate_pack_data
604
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
605
push_result.new_revid = repo.lookup_foreign_revision_id(
608
old_remote = self._refs[refname]
610
old_remote = ZERO_SHA
611
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
612
self._refs = remote_refs_dict_to_container(new_refs)
613
push_result.target_branch = self.open_branch(name)
614
if old_remote != ZERO_SHA:
615
push_result.branch_push_result = GitBranchPushResult()
616
push_result.branch_push_result.source_branch = source
617
push_result.branch_push_result.target_branch = (
618
push_result.target_branch)
619
push_result.branch_push_result.local_branch = None
620
push_result.branch_push_result.master_branch = (
621
push_result.target_branch)
622
push_result.branch_push_result.old_revid = push_result.old_revid
623
push_result.branch_push_result.new_revid = push_result.new_revid
624
push_result.branch_push_result.new_original_revid = (
625
push_result.new_original_revid)
626
if source.get_push_location() is None or remember:
627
source.set_push_location(push_result.target_branch.base)
630
def _find_commondir(self):
631
# There is no way to find the commondir, if there is any.
110
635
class EmptyObjectStoreIterator(dict):
116
641
class TemporaryPackIterator(Pack):
118
643
def __init__(self, path, resolve_ext_ref):
119
self.resolve_ext_ref = resolve_ext_ref
120
super(TemporaryPackIterator, self).__init__(path)
644
super(TemporaryPackIterator, self).__init__(
645
path, resolve_ext_ref=resolve_ext_ref)
646
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)
648
def _idx_load_or_generate(self, path):
649
if not os.path.exists(path):
650
with ui.ui_factory.nested_progress_bar() as pb:
651
def report_progress(cur, total):
652
pb.update("generating index", cur, total)
653
self.data.create_index(path, progress=report_progress)
654
return load_pack_index(path)
129
656
def __del__(self):
130
os.remove(self._data_path)
131
os.remove(self._idx_path)
657
if self._idx is not None:
659
os.remove(self._idx_path)
660
if self._data is not None:
662
os.remove(self._data_path)
665
class BzrGitHttpClient(dulwich.client.HttpGitClient):
667
def __init__(self, transport, *args, **kwargs):
668
self.transport = transport
669
url = urlutils.URL.from_string(transport.external_url())
670
url.user = url.quoted_user = None
671
url.password = url.quoted_password = None
672
url = urlutils.strip_segment_parameters(str(url))
673
super(BzrGitHttpClient, self).__init__(url, *args, **kwargs)
675
def _http_request(self, url, headers=None, data=None,
676
allow_compression=False):
677
"""Perform HTTP request.
679
:param url: Request URL.
680
:param headers: Optional custom headers to override defaults.
681
:param data: Request data.
682
:param allow_compression: Allow GZipped communication.
683
:return: Tuple (`response`, `read`), where response is an `urllib3`
684
response object with additional `content_type` and
685
`redirect_location` properties, and `read` is a consumable read
686
method for the response data.
688
if is_github_url(url):
689
headers['User-agent'] = user_agent_for_github()
690
headers["Pragma"] = "no-cache"
691
if allow_compression:
692
headers["Accept-Encoding"] = "gzip"
694
headers["Accept-Encoding"] = "identity"
696
response = self.transport.request(
697
('GET' if data is None else 'POST'),
700
headers=headers, retries=8)
702
if response.status == 404:
703
raise NotGitRepository()
704
elif response.status != 200:
705
raise GitProtocolError("unexpected http resp %d for %s" %
706
(response.code, url))
708
# TODO: Optimization available by adding `preload_content=False` to the
709
# request and just passing the `read` method on instead of going via
710
# `BytesIO`, if we can guarantee that the entire response is consumed
711
# before issuing the next to still allow for connection reuse from the
713
if response.getheader("Content-Encoding") == "gzip":
714
read = gzip.GzipFile(fileobj=BytesIO(response.read())).read
718
class WrapResponse(object):
720
def __init__(self, response):
721
self._response = response
722
self.status = response.status
723
self.content_type = response.getheader("Content-Type")
724
self.redirect_location = response._actual.geturl()
727
return self._response.readlines()
732
return WrapResponse(response), read
735
def _git_url_and_path_from_transport(external_url):
736
url = urlutils.strip_segment_parameters(external_url)
737
return urlparse.urlsplit(url)
740
class RemoteGitControlDirFormat(GitControlDirFormat):
741
"""The .git directory control format."""
743
supports_workingtrees = False
746
def _known_formats(self):
747
return set([RemoteGitControlDirFormat()])
749
def get_branch_format(self):
750
return RemoteGitBranchFormat()
753
def repository_format(self):
754
return GitRepositoryFormat()
756
def is_initializable(self):
759
def is_supported(self):
762
def open(self, transport, _found=None):
763
"""Open this directory.
766
split_url = _git_url_and_path_from_transport(transport.external_url())
767
if isinstance(transport, GitSmartTransport):
768
client = transport._get_client()
769
elif split_url.scheme in ("http", "https"):
770
client = BzrGitHttpClient(transport)
771
elif split_url.scheme in ('file', ):
772
client = dulwich.client.LocalGitClient()
774
raise NotBranchError(transport.base)
776
pass # TODO(jelmer): Actually probe for something
777
return RemoteGitDir(transport, self, client, split_url.path)
779
def get_format_description(self):
780
return "Remote Git Repository"
782
def initialize_on_transport(self, transport):
783
raise UninitializableFormat(self)
785
def supports_transport(self, transport):
787
external_url = transport.external_url()
788
except InProcessTransport:
789
raise NotBranchError(path=transport.base)
790
return (external_url.startswith("http:")
791
or external_url.startswith("https:")
792
or external_url.startswith("git+")
793
or external_url.startswith("git:"))
796
class GitRemoteRevisionTree(RevisionTree):
798
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
799
"""Create an archive of this tree.
801
:param format: Format name (e.g. 'tar')
802
:param name: target file name
803
:param root: Root directory name (or None)
804
:param subdir: Subdirectory to export (or None)
805
:return: Iterator over archive chunks
807
commit = self._repository.lookup_bzr_revision_id(
808
self.get_revision_id())[0]
809
f = tempfile.SpooledTemporaryFile()
810
# git-upload-archive(1) generaly only supports refs. So let's see if we
814
self._repository.controldir.get_refs_container().as_dict().items()}
816
committish = reverse_refs[commit]
818
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
819
# Let's hope for the best.
821
self._repository.archive(
822
format, committish, f.write,
823
subdirs=([subdir] if subdir else None),
824
prefix=(root + '/') if root else '')
826
return osutils.file_iterator(f)
828
def is_versioned(self, path):
829
raise GitSmartRemoteNotSupported(self.is_versioned, self)
831
def has_filename(self, path):
832
raise GitSmartRemoteNotSupported(self.has_filename, self)
834
def get_file_text(self, path):
835
raise GitSmartRemoteNotSupported(self.get_file_text, self)
837
def list_files(self, include_root=False, from_dir=None, recursive=True):
838
raise GitSmartRemoteNotSupported(self.list_files, self)
134
841
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,
843
supports_random_access = False
847
return self.control_url
849
def get_parent_map(self, revids):
850
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
852
def archive(self, *args, **kwargs):
853
return self.controldir.archive(*args, **kwargs)
855
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):
857
return self.controldir.fetch_pack(
858
determine_wants, graph_walker, pack_data, progress)
860
def send_pack(self, get_changed_refs, generate_pack_data):
861
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
863
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
145
865
fd, path = tempfile.mkstemp(suffix=".pack")
146
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
867
self.fetch_pack(determine_wants, graph_walker,
868
lambda x: os.write(fd, x), progress)
148
871
if os.path.getsize(path) == 0:
149
872
return EmptyObjectStoreIterator()
150
873
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
875
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
876
# This won't work for any round-tripped bzr revisions, but it's a
879
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
880
except InvalidRevisionId:
881
raise NoSuchRevision(self, bzr_revid)
883
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
884
"""Lookup a revision id.
888
mapping = self.get_mapping()
889
# Not really an easy way to parse foreign revids here..
890
return mapping.revision_id_foreign_to_bzr(foreign_revid)
892
def revision_tree(self, revid):
893
return GitRemoteRevisionTree(self, revid)
895
def get_revisions(self, revids):
896
raise GitSmartRemoteNotSupported(self.get_revisions, self)
898
def has_revisions(self, revids):
899
raise GitSmartRemoteNotSupported(self.get_revisions, self)
902
class RemoteGitTagDict(GitTags):
904
def set_tag(self, name, revid):
905
sha = self.branch.lookup_bzr_revision_id(revid)[0]
906
self._set_ref(name, sha)
908
def delete_tag(self, name):
909
self._set_ref(name, dulwich.client.ZERO_SHA)
911
def _set_ref(self, name, sha):
912
ref = tag_name_to_ref(name)
914
def get_changed_refs(old_refs):
916
if sha == dulwich.client.ZERO_SHA and ref not in old_refs:
917
raise NoSuchTag(name)
921
def generate_pack_data(have, want, ofs_delta=False):
922
return pack_objects_to_data([])
923
self.repository.send_pack(get_changed_refs, generate_pack_data)
153
926
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)
928
def __init__(self, controldir, repository, name):
930
super(RemoteGitBranch, self).__init__(controldir, repository, name,
931
RemoteGitBranchFormat())
933
def last_revision_info(self):
934
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
938
return self.control_url
941
def control_url(self):
944
def revision_id_to_revno(self, revision_id):
945
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
164
947
def last_revision(self):
165
return self.mapping.revision_id_foreign_to_bzr(self._ref)
948
return self.lookup_foreign_revision_id(self.head)
952
if self._sha is not None:
954
refs = self.controldir.get_refs_container()
955
name = branch_name_to_ref(self.name)
957
self._sha = refs[name]
959
raise NoSuchRef(name, self.repository.user_url, refs)
167
962
def _synchronize_history(self, destination, revision_id):
168
963
"""See Branch._synchronize_history()."""
169
destination.generate_revision_history(self.last_revision())
964
if revision_id is None:
965
revision_id = self.last_revision()
966
destination.generate_revision_history(revision_id)
968
def _get_parent_location(self):
971
def get_push_location(self):
974
def set_push_location(self, url):
977
def _iter_tag_refs(self):
978
"""Iterate over the tag refs.
980
:param refs: Refs dictionary (name -> git sha1)
981
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
983
refs = self.controldir.get_refs_container()
984
for ref_name, unpeeled in refs.as_dict().items():
986
tag_name = ref_to_tag_name(ref_name)
987
except (ValueError, UnicodeDecodeError):
989
peeled = refs.get_peeled(ref_name)
991
# Let's just hope it's a commit
993
if not isinstance(tag_name, str):
994
raise TypeError(tag_name)
995
yield (ref_name, tag_name, peeled, unpeeled)
997
def set_last_revision_info(self, revno, revid):
998
self.generate_revision_history(revid)
1000
def generate_revision_history(self, revision_id, last_rev=None,
1002
sha = self.lookup_bzr_revision_id(revision_id)[0]
1003
def get_changed_refs(old_refs):
1004
return {self.ref: sha}
1005
def generate_pack_data(have, want, ofs_delta=False):
1006
return pack_objects_to_data([])
1007
self.repository.send_pack(get_changed_refs, generate_pack_data)
1011
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
1014
for k, v in refs_dict.items():
1019
for name, target in symrefs_dict.items():
1020
base[name] = SYMREF + target
1021
ret = DictRefsContainer(base)
1022
ret._peeled = peeled