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.errors import NoSuchRef
29
from bzrlib.plugins.git.dir import GitDir
30
from bzrlib.plugins.git.foreign import ForeignBranch
31
from bzrlib.plugins.git.repository import GitFormat, GitRepository
25
from bzrlib.errors import (
32
from bzrlib.transport import (
36
from bzrlib.plugins.git import (
41
from bzrlib.plugins.git.branch import (
44
from bzrlib.plugins.git.errors import (
45
GitSmartRemoteNotSupported,
48
from bzrlib.plugins.git.dir import (
51
from bzrlib.plugins.git.mapping import (
54
from bzrlib.plugins.git.repository import (
57
from bzrlib.plugins.git.refs import (
63
from dulwich.errors import (
66
from dulwich.pack import (
38
from dulwich.pack import PackData, Pack
74
urlparse.uses_netloc.extend(['git', 'git+ssh'])
76
from dulwich.pack import load_pack_index
79
# Don't run any tests on GitSmartTransport as it is not intended to be
80
# a full implementation of Transport
81
def get_test_permutations():
85
def split_git_url(url):
89
:return: Tuple with host, port, username, path.
91
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
92
path = urllib.unquote(loc)
93
if path.startswith("/~"):
95
(username, hostport) = urllib.splituser(netloc)
96
(host, port) = urllib.splitnport(hostport, None)
97
return (host, port, username, path)
41
100
class GitSmartTransport(Transport):
43
102
def __init__(self, url, _client=None):
44
103
Transport.__init__(self, url)
45
(scheme, _, loc, _, _) = urlparse.urlsplit(url)
46
assert scheme == "git"
47
hostport, self._path = urllib.splithost(loc)
48
(self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
104
(self._host, self._port, self._username, self._path) = \
106
if 'transport' in debug.debug_flags:
107
trace.mutter('host: %r, user: %r, port: %r, path: %r',
108
self._host, self._username, self._port, self._path)
49
109
self._client = _client
51
def _get_client(self):
52
if self._client is not None:
56
return git.client.TCPGitClient(self._host, self._port)
111
def external_url(self):
114
def has(self, relpath):
117
def _get_client(self, thin_packs):
118
raise NotImplementedError(self._get_client)
58
123
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
59
124
if progress is None:
60
125
def progress(text):
61
info("git: %s" % text)
62
self._get_client().fetch_pack(self._path, determine_wants,
63
graph_walker, pack_data, progress)
126
trace.info("git: %s" % text)
127
client = self._get_client(thin_packs=False)
129
return client.fetch_pack(self._get_path(), determine_wants,
130
graph_walker, pack_data, progress)
131
except GitProtocolError, e:
134
def send_pack(self, get_changed_refs, generate_pack_contents):
135
client = self._get_client(thin_packs=False)
137
return client.send_pack(self._get_path(), get_changed_refs,
138
generate_pack_contents)
139
except GitProtocolError, e:
65
142
def get(self, path):
66
143
raise NoSuchFile(path)
76
153
newurl = urlutils.join(self.base, offset)
78
return GitSmartTransport(newurl, self._client)
155
return self.__class__(newurl, self._client)
158
class TCPGitSmartTransport(GitSmartTransport):
162
def _get_client(self, thin_packs):
163
if self._client is not None:
167
return git.client.TCPGitClient(self._host, self._port,
168
thin_packs=thin_packs, report_activity=self._report_activity)
171
class SSHGitSmartTransport(GitSmartTransport):
176
if self._path.startswith("/~/"):
177
return self._path[3:]
180
def _get_client(self, thin_packs):
181
if self._client is not None:
185
location_config = config.LocationConfig(self.base)
186
client = git.client.SSHGitClient(self._host, self._port, self._username,
187
thin_packs=thin_packs, report_activity=self._report_activity)
188
# Set up alternate pack program paths
189
upload_pack = location_config.get_user_option('git_upload_pack')
191
client.alternative_paths["upload-pack"] = upload_pack
192
receive_pack = location_config.get_user_option('git_receive_pack')
194
client.alternative_paths["receive-pack"] = receive_pack
81
198
class RemoteGitDir(GitDir):
85
202
self.root_transport = transport
86
203
self.transport = transport
87
204
self._lockfiles = lockfiles
205
self._mode_check_done = None
207
def _branch_name_to_ref(self, name, default=None):
208
return branch_name_to_ref(name, default=default)
89
210
def open_repository(self):
90
211
return RemoteGitRepository(self, self._lockfiles)
92
def open_branch(self):
213
def _open_branch(self, name=None, ignore_fallbacks=False,
93
215
repo = self.open_repository()
94
# TODO: Support for multiple branches in one bzrdir in bzrlib!
95
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
216
refname = self._branch_name_to_ref(name)
217
return RemoteGitBranch(self, repo, refname, self._lockfiles)
97
def open_workingtree(self):
219
def open_workingtree(self, recommend_upgrade=False):
98
220
raise NotLocalUrl(self.transport.base)
223
class EmptyObjectStoreIterator(dict):
225
def iterobjects(self):
229
class TemporaryPackIterator(Pack):
231
def __init__(self, path, resolve_ext_ref):
232
super(TemporaryPackIterator, self).__init__(path)
233
self.resolve_ext_ref = resolve_ext_ref
237
if self._data is None:
238
self._data = ThinPackData(self.resolve_ext_ref, self._data_path)
243
if self._idx is None:
244
if not os.path.exists(self._idx_path):
245
pb = ui.ui_factory.nested_progress_bar()
247
def report_progress(cur, total):
248
pb.update("generating index", cur, total)
249
self.data.create_index(self._idx_path,
250
progress=report_progress)
253
self._idx = load_pack_index(self._idx_path)
257
if self._idx is not None:
259
os.remove(self._idx_path)
260
if self._data is not None:
262
os.remove(self._data_path)
101
265
class RemoteGitRepository(GitRepository):
103
267
def __init__(self, gitdir, lockfiles):
104
268
GitRepository.__init__(self, gitdir, lockfiles)
106
def fetch_pack(self, determine_wants, graph_walker, pack_data,
272
def inventories(self):
273
raise GitSmartRemoteNotSupported()
277
raise GitSmartRemoteNotSupported()
281
raise GitSmartRemoteNotSupported()
284
if self._refs is not None:
286
self._refs = self.bzrdir.root_transport.fetch_pack(lambda x: [], None,
287
lambda x: None, lambda x: trace.mutter("git: %s" % x))
290
def fetch_pack(self, determine_wants, graph_walker, pack_data,
108
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
111
def fetch_objects(self, determine_wants, graph_walker, progress=None):
292
return self._transport.fetch_pack(determine_wants, graph_walker,
295
def send_pack(self, get_changed_refs, generate_pack_contents):
296
return self._transport.send_pack(get_changed_refs, generate_pack_contents)
298
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
112
300
fd, path = tempfile.mkstemp(suffix=".pack")
113
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
301
self.fetch_pack(determine_wants, graph_walker,
302
lambda x: os.write(fd, x), progress)
304
if os.path.getsize(path) == 0:
305
return EmptyObjectStoreIterator()
306
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
308
def lookup_bzr_revision_id(self, bzr_revid):
309
# This won't work for any round-tripped bzr revisions, but it's a start..
116
basename = path[:-len(".pack")]
118
p.create_index_v2(basename+".idx")
119
for o in Pack(basename).iterobjects():
311
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
312
except InvalidRevisionId:
313
raise NoSuchRevision(self, bzr_revid)
315
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
316
"""Lookup a revision id.
320
mapping = self.get_mapping()
321
# Not really an easy way to parse foreign revids here..
322
return mapping.revision_id_foreign_to_bzr(foreign_revid)
325
class RemoteGitTagDict(tag.BasicTags):
327
def __init__(self, branch):
329
self.repository = branch.repository
331
def get_tag_dict(self):
333
for k, v in extract_tags(self.repository.get_refs()).iteritems():
334
tags[k] = self.branch.mapping.revision_id_foreign_to_bzr(v)
337
def set_tag(self, name, revid):
338
# FIXME: Not supported yet, should do a push of a new ref
339
raise NotImplementedError(self.set_tag)
125
342
class RemoteGitBranch(GitBranch):
127
344
def __init__(self, bzrdir, repository, name, lockfiles):
128
def determine_wants(heads):
129
if not name in heads:
130
raise NoSuchRef(name)
131
self._ref = heads[name]
132
bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None,
133
lambda x: mutter("git: %s" % x))
134
super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
346
super(RemoteGitBranch, self).__init__(bzrdir, repository, name,
349
def revision_history(self):
350
raise GitSmartRemoteNotSupported()
136
352
def last_revision(self):
137
return self.mapping.revision_id_foreign_to_bzr(self._ref)
353
return self.lookup_foreign_revision_id(self.head)
355
def _get_config(self):
356
class EmptyConfig(object):
358
def _get_configobj(self):
359
return config.ConfigObj()
365
if self._sha is not None:
367
heads = self.repository.get_refs()
368
name = self.bzrdir._branch_name_to_ref(self.name, "HEAD")
370
self._sha = heads[name]
372
raise NoSuchRef(self.name)
139
375
def _synchronize_history(self, destination, revision_id):
140
376
"""See Branch._synchronize_history()."""
141
377
destination.generate_revision_history(self.last_revision())
379
def get_push_location(self):
382
def set_push_location(self, url):