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)
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
220
self.root_transport = transport
87
221
self.transport = transport
88
222
self._lockfiles = lockfiles
223
self._mode_check_done = None
227
return self.control_url
90
229
def open_repository(self):
91
230
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
232
def open_branch(self, name=None, unsupported=False,
233
ignore_fallbacks=False):
94
234
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
235
refname = self._get_selected_ref(name)
236
return RemoteGitBranch(self, repo, refname, self._lockfiles)
98
def open_workingtree(self):
238
def open_workingtree(self, recommend_upgrade=False):
99
239
raise NotLocalUrl(self.transport.base)
242
class EmptyObjectStoreIterator(dict):
244
def iterobjects(self):
248
class TemporaryPackIterator(Pack):
250
def __init__(self, path, resolve_ext_ref):
251
super(TemporaryPackIterator, self).__init__(path)
252
self.resolve_ext_ref = resolve_ext_ref
256
if self._data is None:
257
self._data = PackData(self._data_path)
262
if self._idx is None:
263
if not os.path.exists(self._idx_path):
264
pb = ui.ui_factory.nested_progress_bar()
266
def report_progress(cur, total):
267
pb.update("generating index", cur, total)
268
self.data.create_index(self._idx_path,
269
progress=report_progress)
272
self._idx = load_pack_index(self._idx_path)
276
if self._idx is not None:
278
os.remove(self._idx_path)
279
if self._data is not None:
281
os.remove(self._data_path)
284
class RemoteGitControlDirFormat(GitControlDirFormat):
285
"""The .git directory control format."""
287
supports_workingtrees = False
290
def _known_formats(self):
291
return set([RemoteGitControlDirFormat()])
293
def open(self, transport, _found=None):
294
"""Open this directory.
297
# we dont grok readonly - git isn't integrated with transport.
299
if url.startswith('readonly+'):
300
url = url[len('readonly+'):]
301
if (not url.startswith("git://") and not url.startswith("git+")):
302
raise NotBranchError(transport.base)
303
if not isinstance(transport, GitSmartTransport):
304
raise NotBranchError(transport.base)
305
lockfiles = GitLockableFiles(transport, GitLock())
306
return RemoteGitDir(transport, lockfiles, self)
308
def get_format_description(self):
309
return "Remote Git Repository"
311
def initialize_on_transport(self, transport):
312
raise UninitializableFormat(self)
102
315
class RemoteGitRepository(GitRepository):
104
317
def __init__(self, gitdir, lockfiles):
105
318
GitRepository.__init__(self, gitdir, lockfiles)
107
def fetch_pack(self, determine_wants, graph_walker, pack_data,
323
return self.control_url
325
def get_parent_map(self, revids):
326
raise GitSmartRemoteNotSupported()
329
if self._refs is not None:
331
self._refs = self.bzrdir.root_transport.fetch_pack(lambda x: [], None,
332
lambda x: None, lambda x: trace.mutter("git: %s" % x))
335
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
337
return self._transport.fetch_pack(determine_wants, graph_walker,
340
def send_pack(self, get_changed_refs, generate_pack_contents):
341
return self._transport.send_pack(get_changed_refs, generate_pack_contents)
343
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
345
fd, path = tempfile.mkstemp(suffix=".pack")
347
self.fetch_pack(determine_wants, graph_walker,
348
lambda x: os.write(fd, x), progress)
351
if os.path.getsize(path) == 0:
352
return EmptyObjectStoreIterator()
353
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
355
def lookup_bzr_revision_id(self, bzr_revid):
356
# This won't work for any round-tripped bzr revisions, but it's a start..
358
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
359
except InvalidRevisionId:
360
raise NoSuchRevision(self, bzr_revid)
362
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
363
"""Lookup a revision id.
367
mapping = self.get_mapping()
368
# Not really an easy way to parse foreign revids here..
369
return mapping.revision_id_foreign_to_bzr(foreign_revid)
372
class RemoteGitTagDict(GitTags):
375
return self.repository.get_refs()
377
def _iter_tag_refs(self, refs):
378
for k, (peeled, unpeeled) in extract_tags(refs).iteritems():
379
yield (k, peeled, unpeeled,
380
self.branch.mapping.revision_id_foreign_to_bzr(peeled))
382
def set_tag(self, name, revid):
383
# FIXME: Not supported yet, should do a push of a new ref
384
raise NotImplementedError(self.set_tag)
113
387
class RemoteGitBranch(GitBranch):
115
389
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)
391
super(RemoteGitBranch, self).__init__(bzrdir, repository, name,
396
return self.control_url
399
def control_url(self):
402
def revision_history(self):
403
raise GitSmartRemoteNotSupported()
122
405
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
406
return self.lookup_foreign_revision_id(self.head)
408
def _get_config(self):
409
class EmptyConfig(object):
411
def _get_configobj(self):
412
return config.ConfigObj()
418
if self._sha is not None:
420
heads = self.repository.get_refs()
421
name = branch_name_to_ref(self.name, "HEAD")
423
self._sha = heads[name]
425
raise NoSuchRef(self.name)
428
def _synchronize_history(self, destination, revision_id):
429
"""See Branch._synchronize_history()."""
430
destination.generate_revision_history(self.last_revision())
432
def get_push_location(self):
435
def set_push_location(self, url):