1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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
17
"""Server-side repository related request implmentations."""
19
from cStringIO import StringIO
25
from bzrlib import errors
26
from bzrlib.bzrdir import BzrDir
27
from bzrlib.pack import ContainerWriter
28
from bzrlib.smart.request import (
29
FailedSmartServerResponse,
31
SuccessfulSmartServerResponse,
35
class SmartServerRepositoryRequest(SmartServerRequest):
36
"""Common base class for Repository requests."""
38
def do(self, path, *args):
39
"""Execute a repository request.
41
The repository must be at the exact path - no searching is done.
43
The actual logic is delegated to self.do_repository_request.
45
:param path: The path for the repository.
46
:return: A smart server from self.do_repository_request().
48
transport = self._backing_transport.clone(path)
49
bzrdir = BzrDir.open_from_transport(transport)
50
repository = bzrdir.open_repository()
51
return self.do_repository_request(repository, *args)
54
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryRequest):
56
def do_repository_request(self, repository, revision_id):
57
"""Return the result of repository.get_revision_graph(revision_id).
59
:param repository: The repository to query in.
60
:param revision_id: The utf8 encoded revision_id to get a graph from.
61
:return: A smart server response where the body contains an utf8
62
encoded flattened list of the revision graph.
69
revision_graph = repository.get_revision_graph(revision_id)
70
except errors.NoSuchRevision:
71
# Note that we return an empty body, rather than omitting the body.
72
# This way the client knows that it can always expect to find a body
73
# in the response for this method, even in the error case.
74
return FailedSmartServerResponse(('nosuchrevision', revision_id), '')
76
for revision, parents in revision_graph.items():
77
lines.append(' '.join((revision, ) + tuple(parents)))
79
return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
82
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
84
def do_repository_request(self, repository, revision_id):
85
"""Return ok if a specific revision is in the repository at path.
87
:param repository: The repository to query in.
88
:param revision_id: The utf8 encoded revision_id to lookup.
89
:return: A smart server response of ('ok', ) if the revision is
92
if repository.has_revision(revision_id):
93
return SuccessfulSmartServerResponse(('yes', ))
95
return SuccessfulSmartServerResponse(('no', ))
98
class SmartServerRepositoryGatherStats(SmartServerRepositoryRequest):
100
def do_repository_request(self, repository, revid, committers):
101
"""Return the result of repository.gather_stats().
103
:param repository: The repository to query in.
104
:param revid: utf8 encoded rev id or an empty string to indicate None
105
:param committers: 'yes' or 'no'.
107
:return: A SmartServerResponse ('ok',), a encoded body looking like
110
latestrev: 345.700 3600
114
But containing only fields returned by the gather_stats() call
117
decoded_revision_id = None
119
decoded_revision_id = revid
120
if committers == 'yes':
121
decoded_committers = True
123
decoded_committers = None
124
stats = repository.gather_stats(decoded_revision_id, decoded_committers)
127
if stats.has_key('committers'):
128
body += 'committers: %d\n' % stats['committers']
129
if stats.has_key('firstrev'):
130
body += 'firstrev: %.3f %d\n' % stats['firstrev']
131
if stats.has_key('latestrev'):
132
body += 'latestrev: %.3f %d\n' % stats['latestrev']
133
if stats.has_key('revisions'):
134
body += 'revisions: %d\n' % stats['revisions']
135
if stats.has_key('size'):
136
body += 'size: %d\n' % stats['size']
138
return SuccessfulSmartServerResponse(('ok', ), body)
141
class SmartServerRepositoryIsShared(SmartServerRepositoryRequest):
143
def do_repository_request(self, repository):
144
"""Return the result of repository.is_shared().
146
:param repository: The repository to query in.
147
:return: A smart server response of ('yes', ) if the repository is
148
shared, and ('no', ) if it is not.
150
if repository.is_shared():
151
return SuccessfulSmartServerResponse(('yes', ))
153
return SuccessfulSmartServerResponse(('no', ))
156
class SmartServerRepositoryLockWrite(SmartServerRepositoryRequest):
158
def do_repository_request(self, repository, token=''):
159
# XXX: this probably should not have a token.
163
token = repository.lock_write(token=token)
164
except errors.LockContention, e:
165
return FailedSmartServerResponse(('LockContention',))
166
except errors.UnlockableTransport:
167
return FailedSmartServerResponse(('UnlockableTransport',))
168
except errors.LockFailed, e:
169
return FailedSmartServerResponse(('LockFailed',
170
str(e.lock), str(e.why)))
171
repository.leave_lock_in_place()
175
return SuccessfulSmartServerResponse(('ok', token))
178
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
180
def do_repository_request(self, repository, token):
182
repository.lock_write(token=token)
183
except errors.TokenMismatch, e:
184
return FailedSmartServerResponse(('TokenMismatch',))
185
repository.dont_leave_lock_in_place()
187
return SuccessfulSmartServerResponse(('ok',))
190
class SmartServerRepositoryTarball(SmartServerRepositoryRequest):
191
"""Get the raw repository files as a tarball.
193
The returned tarball contains a .bzr control directory which in turn
194
contains a repository.
196
This takes one parameter, compression, which currently must be
199
This is used to implement the Repository.copy_content_into operation.
202
def do_repository_request(self, repository, compression):
203
from bzrlib import osutils
204
repo_transport = repository.control_files._transport
205
tmp_dirname, tmp_repo = self._copy_to_tempdir(repository)
207
controldir_name = tmp_dirname + '/.bzr'
208
return self._tarfile_response(controldir_name, compression)
210
osutils.rmtree(tmp_dirname)
212
def _copy_to_tempdir(self, from_repo):
213
tmp_dirname = tempfile.mkdtemp(prefix='tmpbzrclone')
214
tmp_bzrdir = from_repo.bzrdir._format.initialize(tmp_dirname)
215
tmp_repo = from_repo._format.initialize(tmp_bzrdir)
216
from_repo.copy_content_into(tmp_repo)
217
return tmp_dirname, tmp_repo
219
def _tarfile_response(self, tmp_dirname, compression):
220
temp = tempfile.NamedTemporaryFile()
222
self._tarball_of_dir(tmp_dirname, compression, temp.file)
223
# all finished; write the tempfile out to the network
225
return SuccessfulSmartServerResponse(('ok',), temp.read())
226
# FIXME: Don't read the whole thing into memory here; rather stream it
227
# out from the file onto the network. mbp 20070411
231
def _tarball_of_dir(self, dirname, compression, ofile):
232
filename = os.path.basename(ofile.name)
233
tarball = tarfile.open(fileobj=ofile, name=filename,
234
mode='w|' + compression)
236
# The tarball module only accepts ascii names, and (i guess)
237
# packs them with their 8bit names. We know all the files
238
# within the repository have ASCII names so the should be safe
240
dirname = dirname.encode(sys.getfilesystemencoding())
241
# python's tarball module includes the whole path by default so
243
assert dirname.endswith('.bzr')
244
tarball.add(dirname, '.bzr') # recursive by default
249
class SmartServerRepositoryStreamKnitDataForRevisions(SmartServerRepositoryRequest):
251
def do_repository_request(self, repository, *revision_ids):
252
stream = repository.get_data_stream(revision_ids)
253
filelike = StringIO()
254
pack = ContainerWriter(filelike.write)
257
for name_tuple, bytes in stream:
258
pack.add_bytes_record(bytes, [name_tuple])
259
except errors.RevisionNotPresent, e:
260
return FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
262
return SuccessfulSmartServerResponse(('ok',), filelike.getvalue())