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)
38
105
class GitSmartTransport(Transport):
40
107
def __init__(self, url, _client=None):
41
108
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)
109
(self._host, self._port, self._username, self._path) = \
111
if 'transport' in debug.debug_flags:
112
trace.mutter('host: %r, user: %r, port: %r, path: %r',
113
self._host, self._username, self._port, self._path)
114
self._client = _client
116
def external_url(self):
119
def has(self, relpath):
122
def _get_client(self, thin_packs):
123
raise NotImplementedError(self._get_client)
51
128
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
52
129
if progress is None:
53
130
def progress(text):
54
info("git: %s" % text)
55
self._client.fetch_pack(self._path, determine_wants, graph_walker,
131
trace.info("git: %s" % text)
132
client = self._get_client(thin_packs=False)
134
return client.fetch_pack(self._get_path(), determine_wants,
135
graph_walker, pack_data, progress)
136
except GitProtocolError, 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)
139
def send_pack(self, get_changed_refs, generate_pack_contents):
140
client = self._get_client(thin_packs=False)
64
for o in p.iterobjects():
142
return client.send_pack(self._get_path(), get_changed_refs,
143
generate_pack_contents)
144
except GitProtocolError, e:
69
147
def get(self, path):
70
148
raise NoSuchFile(path)
150
def abspath(self, relpath):
151
return urlutils.join(self.base, relpath)
72
153
def clone(self, offset=None):
73
154
"""See Transport.clone()."""
74
155
if offset is None:
86
207
self.root_transport = transport
87
208
self.transport = transport
88
209
self._lockfiles = lockfiles
210
self._mode_check_done = None
214
return self.control_url
216
def _branch_name_to_ref(self, name, default=None):
217
return branch_name_to_ref(name, default=default)
90
219
def open_repository(self):
91
220
return RemoteGitRepository(self, self._lockfiles)
93
def open_branch(self):
222
def open_branch(self, name=None, unsupported=False, ignore_fallbacks=False):
94
223
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
224
refname = self._branch_name_to_ref(name)
225
return RemoteGitBranch(self, repo, refname, self._lockfiles)
98
def open_workingtree(self):
227
def open_workingtree(self, recommend_upgrade=False):
99
228
raise NotLocalUrl(self.transport.base)
231
class EmptyObjectStoreIterator(dict):
233
def iterobjects(self):
237
class TemporaryPackIterator(Pack):
239
def __init__(self, path, resolve_ext_ref):
240
super(TemporaryPackIterator, self).__init__(path)
241
self.resolve_ext_ref = resolve_ext_ref
245
if self._data is None:
246
self._data = ThinPackData(self.resolve_ext_ref, self._data_path)
251
if self._idx is None:
252
if not os.path.exists(self._idx_path):
253
pb = ui.ui_factory.nested_progress_bar()
255
def report_progress(cur, total):
256
pb.update("generating index", cur, total)
257
self.data.create_index(self._idx_path,
258
progress=report_progress)
261
self._idx = load_pack_index(self._idx_path)
265
if self._idx is not None:
267
os.remove(self._idx_path)
268
if self._data is not None:
270
os.remove(self._data_path)
273
class RemoteGitControlDirFormat(GitControlDirFormat):
274
"""The .git directory control format."""
276
supports_workingtrees = False
279
def _known_formats(self):
280
return set([RemoteGitControlDirFormat()])
282
def open(self, transport, _found=None):
283
"""Open this directory.
286
# we dont grok readonly - git isn't integrated with transport.
288
if url.startswith('readonly+'):
289
url = url[len('readonly+'):]
290
if (not url.startswith("git://") and not url.startswith("git+")):
291
raise NotBranchError(transport.base)
292
if not isinstance(transport, GitSmartTransport):
293
raise NotBranchError(transport.base)
294
lockfiles = GitLockableFiles(transport, GitLock())
295
return RemoteGitDir(transport, lockfiles, self)
297
def get_format_description(self):
298
return "Remote Git Repository"
300
def initialize_on_transport(self, transport):
301
raise UninitializableFormat(self)
102
304
class RemoteGitRepository(GitRepository):
104
306
def __init__(self, gitdir, lockfiles):
105
307
GitRepository.__init__(self, gitdir, lockfiles)
107
def fetch_pack(self, determine_wants, graph_walker, pack_data,
312
return self.control_url
315
def inventories(self):
316
raise GitSmartRemoteNotSupported()
320
raise GitSmartRemoteNotSupported()
324
raise GitSmartRemoteNotSupported()
327
if self._refs is not None:
329
self._refs = self.bzrdir.root_transport.fetch_pack(lambda x: [], None,
330
lambda x: None, lambda x: trace.mutter("git: %s" % x))
333
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
335
return self._transport.fetch_pack(determine_wants, graph_walker,
338
def send_pack(self, get_changed_refs, generate_pack_contents):
339
return self._transport.send_pack(get_changed_refs, generate_pack_contents)
341
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
343
fd, path = tempfile.mkstemp(suffix=".pack")
344
self.fetch_pack(determine_wants, graph_walker,
345
lambda x: os.write(fd, x), progress)
347
if os.path.getsize(path) == 0:
348
return EmptyObjectStoreIterator()
349
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
351
def lookup_bzr_revision_id(self, bzr_revid):
352
# This won't work for any round-tripped bzr revisions, but it's a start..
354
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
355
except InvalidRevisionId:
356
raise NoSuchRevision(self, bzr_revid)
358
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
359
"""Lookup a revision id.
363
mapping = self.get_mapping()
364
# Not really an easy way to parse foreign revids here..
365
return mapping.revision_id_foreign_to_bzr(foreign_revid)
368
class RemoteGitTagDict(GitTags):
371
return self.repository.get_refs()
373
def _iter_tag_refs(self, refs):
374
for k, (peeled, unpeeled) in extract_tags(refs).iteritems():
375
yield (k, peeled, unpeeled,
376
self.branch.mapping.revision_id_foreign_to_bzr(peeled))
378
def set_tag(self, name, revid):
379
# FIXME: Not supported yet, should do a push of a new ref
380
raise NotImplementedError(self.set_tag)
113
383
class RemoteGitBranch(GitBranch):
115
385
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)
387
super(RemoteGitBranch, self).__init__(bzrdir, repository, name,
392
return self.control_url
395
def control_url(self):
398
def revision_history(self):
399
raise GitSmartRemoteNotSupported()
122
401
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
402
return self.lookup_foreign_revision_id(self.head)
404
def _get_config(self):
405
class EmptyConfig(object):
407
def _get_configobj(self):
408
return config.ConfigObj()
414
if self._sha is not None:
416
heads = self.repository.get_refs()
417
name = self.bzrdir._branch_name_to_ref(self.name, "HEAD")
419
self._sha = heads[name]
421
raise NoSuchRef(self.name)
424
def _synchronize_history(self, destination, revision_id):
425
"""See Branch._synchronize_history()."""
426
destination.generate_revision_history(self.last_revision())
428
def get_push_location(self):
431
def set_push_location(self, url):