1
# Copyright (C) 2006-2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Server-side branch related request implmentations."""
24
from bzrlib.bzrdir import BzrDir
25
from bzrlib.smart.request import (
26
FailedSmartServerResponse,
28
SuccessfulSmartServerResponse,
32
class SmartServerBranchRequest(SmartServerRequest):
33
"""Base class for handling common branch request logic.
36
def do(self, path, *args):
37
"""Execute a request for a branch at path.
39
All Branch requests take a path to the branch as their first argument.
41
If the branch is a branch reference, NotBranchError is raised.
43
:param path: The path for the repository as received from the
45
:return: A SmartServerResponse from self.do_with_branch().
47
transport = self.transport_from_client_path(path)
48
bzrdir = BzrDir.open_from_transport(transport)
49
if bzrdir.get_branch_reference() is not None:
50
raise errors.NotBranchError(transport.base)
51
branch = bzrdir.open_branch(ignore_fallbacks=True)
52
return self.do_with_branch(branch, *args)
55
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
56
"""Base class for handling common branch request logic for requests that
60
def do_with_branch(self, branch, branch_token, repo_token, *args):
61
"""Execute a request for a branch.
63
A write lock will be acquired with the given tokens for the branch and
64
repository locks. The lock will be released once the request is
65
processed. The physical lock state won't be changed.
67
# XXX: write a test for LockContention
68
branch.repository.lock_write(token=repo_token)
70
branch.lock_write(token=branch_token)
72
return self.do_with_locked_branch(branch, *args)
76
branch.repository.unlock()
79
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
81
def do_with_branch(self, branch):
82
"""Return the content of branch.conf
84
The body is not utf8 decoded - its the literal bytestream from disk.
87
content = branch._transport.get_bytes('branch.conf')
88
except errors.NoSuchFile:
90
return SuccessfulSmartServerResponse( ('ok', ), content)
93
class SmartServerBranchGetParent(SmartServerBranchRequest):
95
def do_with_branch(self, branch):
96
"""Return the parent of branch."""
97
parent = branch._get_parent_location() or ''
98
return SuccessfulSmartServerResponse((parent,))
101
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
103
def do_with_branch(self, branch):
104
"""Return the _get_tags_bytes for a branch."""
105
bytes = branch._get_tags_bytes()
106
return SuccessfulSmartServerResponse((bytes,))
109
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
111
def __init__(self, backing_transport, root_client_path='/', jail_root=None):
112
SmartServerLockedBranchRequest.__init__(
113
self, backing_transport, root_client_path, jail_root)
116
def do_with_locked_branch(self, branch):
117
"""Call _set_tags_bytes for a branch.
121
# We need to keep this branch locked until we get a body with the tags
124
self.branch.lock_write()
127
def do_body(self, bytes):
128
self.branch._set_tags_bytes(bytes)
129
return SuccessfulSmartServerResponse(())
132
# TODO: this request shouldn't have to do this housekeeping manually.
133
# Some of this logic probably belongs in a base class.
135
# We never acquired the branch successfully in the first place, so
136
# there's nothing more to do.
139
return SmartServerLockedBranchRequest.do_end(self)
141
# Only try unlocking if we locked successfully in the first place
145
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
147
def do_with_branch(self, branch):
148
stacked_on_url = branch.get_stacked_on_url()
149
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
152
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
154
def do_with_branch(self, branch):
155
"""Get the revision history for the branch.
157
The revision list is returned as the body content,
158
with each revision utf8 encoded and \x00 joined.
160
return SuccessfulSmartServerResponse(
161
('ok', ), ('\x00'.join(branch.revision_history())))
164
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
166
def do_with_branch(self, branch):
167
"""Return branch.last_revision_info().
169
The revno is encoded in decimal, the revision_id is encoded as utf8.
171
revno, last_revision = branch.last_revision_info()
172
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
175
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
176
"""Base class for handling common branch request logic for requests that
177
update the branch tip.
180
def do_with_locked_branch(self, branch, *args):
182
return self.do_tip_change_with_locked_branch(branch, *args)
183
except errors.TipChangeRejected, e:
185
if isinstance(msg, unicode):
186
msg = msg.encode('utf-8')
187
return FailedSmartServerResponse(('TipChangeRejected', msg))
190
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
191
"""Set an option in the branch configuration."""
193
def do_with_locked_branch(self, branch, value, name, section):
196
branch._get_config().set_option(value.decode('utf8'), name, section)
197
return SuccessfulSmartServerResponse(())
200
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
201
"""Set an option in the branch configuration.
206
def do_with_locked_branch(self, branch, value_dict, name, section):
207
utf8_dict = bencode.bdecode(value_dict)
209
for key, value in utf8_dict.items():
210
value_dict[key.decode('utf8')] = value.decode('utf8')
213
branch._get_config().set_option(value_dict, name, section)
214
return SuccessfulSmartServerResponse(())
217
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
219
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
220
if new_last_revision_id == 'null:':
221
branch.set_revision_history([])
223
if not branch.repository.has_revision(new_last_revision_id):
224
return FailedSmartServerResponse(
225
('NoSuchRevision', new_last_revision_id))
226
branch.set_revision_history(branch._lefthand_history(
227
new_last_revision_id, None, None))
228
return SuccessfulSmartServerResponse(('ok',))
231
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
233
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
234
allow_divergence, allow_overwrite_descendant):
235
"""Set the last revision of the branch.
239
:param new_last_revision_id: the revision ID to set as the last
240
revision of the branch.
241
:param allow_divergence: A flag. If non-zero, change the revision ID
242
even if the new_last_revision_id's ancestry has diverged from the
243
current last revision. If zero, a 'Diverged' error will be
244
returned if new_last_revision_id is not a descendant of the current
246
:param allow_overwrite_descendant: A flag. If zero and
247
new_last_revision_id is not a descendant of the current last
248
revision, then the last revision will not be changed. If non-zero
249
and there is no divergence, then the last revision is always
252
:returns: on success, a tuple of ('ok', revno, revision_id), where
253
revno and revision_id are the new values of the current last
254
revision info. The revision_id might be different to the
255
new_last_revision_id if allow_overwrite_descendant was not set.
257
do_not_overwrite_descendant = not allow_overwrite_descendant
259
last_revno, last_rev = branch.last_revision_info()
260
graph = branch.repository.get_graph()
261
if not allow_divergence or do_not_overwrite_descendant:
262
relation = branch._revision_relations(
263
last_rev, new_last_revision_id, graph)
264
if relation == 'diverged' and not allow_divergence:
265
return FailedSmartServerResponse(('Diverged',))
266
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
267
return SuccessfulSmartServerResponse(
268
('ok', last_revno, last_rev))
269
new_revno = graph.find_distance_to_null(
270
new_last_revision_id, [(last_rev, last_revno)])
271
branch.set_last_revision_info(new_revno, new_last_revision_id)
272
except errors.GhostRevisionsHaveNoRevno:
273
return FailedSmartServerResponse(
274
('NoSuchRevision', new_last_revision_id))
275
return SuccessfulSmartServerResponse(
276
('ok', new_revno, new_last_revision_id))
279
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
280
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
281
the specified branch.
286
def do_tip_change_with_locked_branch(self, branch, new_revno,
287
new_last_revision_id):
289
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
290
except errors.NoSuchRevision:
291
return FailedSmartServerResponse(
292
('NoSuchRevision', new_last_revision_id))
293
return SuccessfulSmartServerResponse(('ok',))
296
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
297
"""Set the parent location for a branch.
299
Takes a location to set, which must be utf8 encoded.
302
def do_with_locked_branch(self, branch, location):
303
branch._set_parent_location(location)
304
return SuccessfulSmartServerResponse(())
307
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
309
def do_with_branch(self, branch, branch_token='', repo_token=''):
310
if branch_token == '':
315
repo_token = branch.repository.lock_write(
316
token=repo_token).repository_token
318
branch_token = branch.lock_write(
319
token=branch_token).branch_token
321
# this leaves the repository with 1 lock
322
branch.repository.unlock()
323
except errors.LockContention:
324
return FailedSmartServerResponse(('LockContention',))
325
except errors.TokenMismatch:
326
return FailedSmartServerResponse(('TokenMismatch',))
327
except errors.UnlockableTransport:
328
return FailedSmartServerResponse(('UnlockableTransport',))
329
except errors.LockFailed, e:
330
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
331
if repo_token is None:
334
branch.repository.leave_lock_in_place()
335
branch.leave_lock_in_place()
337
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
340
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
342
def do_with_branch(self, branch, branch_token, repo_token):
344
branch.repository.lock_write(token=repo_token)
346
branch.lock_write(token=branch_token)
348
branch.repository.unlock()
349
except errors.TokenMismatch:
350
return FailedSmartServerResponse(('TokenMismatch',))
352
branch.repository.dont_leave_lock_in_place()
353
branch.dont_leave_lock_in_place()
355
return SuccessfulSmartServerResponse(('ok',))