14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from bzrlib import urlutils
19
from bzrlib.bzrdir import BzrDir, BzrDirFormat
20
from bzrlib.errors import NoSuchFile, NotLocalUrl
21
from bzrlib.lockable_files import TransportLock
22
from bzrlib.repository import Repository
23
from bzrlib.trace import info
24
from bzrlib.transport import Transport
26
from bzrlib.plugins.git import git
27
from bzrlib.plugins.git.branch import GitBranch
28
from bzrlib.plugins.git.dir import GitDir
29
from bzrlib.plugins.git.foreign import ForeignBranch
30
from bzrlib.plugins.git.repository import GitFormat, GitRepository
24
from bzrlib.errors import (
31
UninitializableFormat,
33
from bzrlib.transport import (
37
from bzrlib.plugins.git import (
42
from bzrlib.plugins.git.branch import (
46
from bzrlib.plugins.git.dir import (
52
from bzrlib.plugins.git.errors import (
53
GitSmartRemoteNotSupported,
56
from bzrlib.plugins.git.mapping import (
59
from bzrlib.plugins.git.repository import (
62
from bzrlib.plugins.git.refs import (
68
from dulwich.errors import (
71
from dulwich.pack import (
35
from dulwich.pack import PackData
79
urlparse.uses_netloc.extend(['git', 'git+ssh'])
81
from dulwich.pack import load_pack_index
84
# Don't run any tests on GitSmartTransport as it is not intended to be
85
# a full implementation of Transport
86
def get_test_permutations():
90
def split_git_url(url):
94
:return: Tuple with host, port, username, path.
96
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
97
path = urllib.unquote(loc)
98
if path.startswith("/~"):
100
(username, hostport) = urllib.splituser(netloc)
101
(host, port) = urllib.splitnport(hostport, None)
102
return (host, port, username, path)
105
def parse_git_error(url, message):
106
"""Parse a remote git server error and return a bzr exception.
108
:param url: URL of the remote repository
109
:param message: Message sent by the remote git server
111
message = str(message).strip()
112
if message.startswith("Could not find Repository "):
113
return NotBranchError(url, message)
114
# Don't know, just return it to the user as-is
115
return BzrError(message)
38
118
class GitSmartTransport(Transport):
40
120
def __init__(self, url, _client=None):
41
121
Transport.__init__(self, url)
42
(scheme, _, loc, _, _) = urlparse.urlsplit(url)
43
assert scheme == "git"
44
hostport, self._path = urllib.splithost(loc)
45
(self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
46
if _client is not None:
47
self._client = _client
49
self._client = git.client.TCPGitClient(self._host, self._port)
122
(self._host, self._port, self._username, self._path) = \
124
if 'transport' in debug.debug_flags:
125
trace.mutter('host: %r, user: %r, port: %r, path: %r',
126
self._host, self._username, self._port, self._path)
127
self._client = _client
129
def external_url(self):
132
def has(self, relpath):
135
def _get_client(self, thin_packs):
136
raise NotImplementedError(self._get_client)
139
return urlutils.split_segment_parameters_raw(self._path)[0]
51
141
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
52
142
if progress is None:
53
143
def progress(text):
54
info("git: %s" % text)
55
self._client.fetch_pack(self._path, determine_wants, graph_walker,
144
trace.info("git: %s" % text)
145
client = self._get_client(thin_packs=False)
147
return client.fetch_pack(self._get_path(), determine_wants,
148
graph_walker, pack_data, progress)
149
except GitProtocolError, e:
150
raise parse_git_error(self.external_url(), e)
58
def fetch_objects(self, determine_wants, graph_walker, progress=None):
59
fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
60
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
152
def send_pack(self, get_changed_refs, generate_pack_contents):
153
client = self._get_client(thin_packs=False)
64
for o in p.iterobjects():
155
return client.send_pack(self._get_path(), get_changed_refs,
156
generate_pack_contents)
157
except GitProtocolError, e:
158
raise parse_git_error(self.external_url(), e)
69
160
def get(self, path):
70
161
raise NoSuchFile(path)
163
def abspath(self, relpath):
164
return urlutils.join(self.base, relpath)
72
166
def clone(self, offset=None):
73
167
"""See Transport.clone()."""
74
168
if offset is None:
86
221
self.root_transport = transport
87
222
self.transport = transport
88
223
self._lockfiles = lockfiles
224
self._mode_check_done = None
228
return self.control_url
231
def user_transport(self):
232
return self.root_transport
90
234
def open_repository(self):
91
235
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
237
def open_branch(self, name=None, unsupported=False,
238
ignore_fallbacks=False):
94
239
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
240
refname = self._get_selected_ref(name)
241
return RemoteGitBranch(self, repo, refname, self._lockfiles)
98
def open_workingtree(self):
243
def open_workingtree(self, recommend_upgrade=False):
99
244
raise NotLocalUrl(self.transport.base)
247
class EmptyObjectStoreIterator(dict):
249
def iterobjects(self):
253
class TemporaryPackIterator(Pack):
255
def __init__(self, path, resolve_ext_ref):
256
super(TemporaryPackIterator, self).__init__(path)
257
self.resolve_ext_ref = resolve_ext_ref
261
if self._data is None:
262
self._data = PackData(self._data_path)
267
if self._idx is None:
268
if not os.path.exists(self._idx_path):
269
pb = ui.ui_factory.nested_progress_bar()
271
def report_progress(cur, total):
272
pb.update("generating index", cur, total)
273
self.data.create_index(self._idx_path,
274
progress=report_progress)
277
self._idx = load_pack_index(self._idx_path)
281
if self._idx is not None:
283
os.remove(self._idx_path)
284
if self._data is not None:
286
os.remove(self._data_path)
289
class RemoteGitControlDirFormat(GitControlDirFormat):
290
"""The .git directory control format."""
292
supports_workingtrees = False
295
def _known_formats(self):
296
return set([RemoteGitControlDirFormat()])
298
def open(self, transport, _found=None):
299
"""Open this directory.
302
# we dont grok readonly - git isn't integrated with transport.
304
if url.startswith('readonly+'):
305
url = url[len('readonly+'):]
306
if (not url.startswith("git://") and not url.startswith("git+")):
307
raise NotBranchError(transport.base)
308
if not isinstance(transport, GitSmartTransport):
309
raise NotBranchError(transport.base)
310
lockfiles = GitLockableFiles(transport, GitLock())
311
return RemoteGitDir(transport, lockfiles, self)
313
def get_format_description(self):
314
return "Remote Git Repository"
316
def initialize_on_transport(self, transport):
317
raise UninitializableFormat(self)
102
320
class RemoteGitRepository(GitRepository):
104
322
def __init__(self, gitdir, lockfiles):
105
323
GitRepository.__init__(self, gitdir, lockfiles)
107
def fetch_pack(self, determine_wants, graph_walker, pack_data,
328
return self.control_url
330
def get_parent_map(self, revids):
331
raise GitSmartRemoteNotSupported()
334
if self._refs is not None:
336
self._refs = self.bzrdir.root_transport.fetch_pack(lambda x: [], None,
337
lambda x: None, lambda x: trace.mutter("git: %s" % x))
340
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
342
return self._transport.fetch_pack(determine_wants, graph_walker,
345
def send_pack(self, get_changed_refs, generate_pack_contents):
346
return self._transport.send_pack(get_changed_refs, generate_pack_contents)
348
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
350
fd, path = tempfile.mkstemp(suffix=".pack")
352
self.fetch_pack(determine_wants, graph_walker,
353
lambda x: os.write(fd, x), progress)
356
if os.path.getsize(path) == 0:
357
return EmptyObjectStoreIterator()
358
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
360
def lookup_bzr_revision_id(self, bzr_revid):
361
# This won't work for any round-tripped bzr revisions, but it's a start..
363
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
364
except InvalidRevisionId:
365
raise NoSuchRevision(self, bzr_revid)
367
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
368
"""Lookup a revision id.
372
mapping = self.get_mapping()
373
# Not really an easy way to parse foreign revids here..
374
return mapping.revision_id_foreign_to_bzr(foreign_revid)
377
class RemoteGitTagDict(GitTags):
380
return self.repository.get_refs()
382
def _iter_tag_refs(self, refs):
383
for k, (peeled, unpeeled) in extract_tags(refs).iteritems():
384
yield (k, peeled, unpeeled,
385
self.branch.mapping.revision_id_foreign_to_bzr(peeled))
387
def set_tag(self, name, revid):
388
# FIXME: Not supported yet, should do a push of a new ref
389
raise NotImplementedError(self.set_tag)
113
392
class RemoteGitBranch(GitBranch):
115
394
def __init__(self, bzrdir, repository, name, lockfiles):
116
def determine_wants(heads):
117
self._ref = heads[name]
118
bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None,
119
lambda x: mutter("git: %s" % x))
120
super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
396
super(RemoteGitBranch, self).__init__(bzrdir, repository, name,
399
def last_revision_info(self):
400
raise GitSmartRemoteNotSupported()
404
return self.control_url
407
def control_url(self):
410
def revision_history(self):
411
raise GitSmartRemoteNotSupported()
122
413
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
414
return self.lookup_foreign_revision_id(self.head)
416
def _get_config(self):
417
class EmptyConfig(object):
419
def _get_configobj(self):
420
return config.ConfigObj()
426
if self._sha is not None:
428
heads = self.repository.get_refs()
429
name = branch_name_to_ref(self.name, "HEAD")
431
self._sha = heads[name]
433
raise NoSuchRef(self.name)
436
def _synchronize_history(self, destination, revision_id):
437
"""See Branch._synchronize_history()."""
438
destination.generate_revision_history(self.last_revision())
440
def get_push_location(self):
443
def set_push_location(self, url):