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 SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
147
def do_with_branch(self, branch):
148
"""Return the heads-to-fetch for a Branch as two bencoded lists.
150
See Branch.heads_to_fetch.
154
must_fetch, if_present_fetch = branch.heads_to_fetch()
155
return SuccessfulSmartServerResponse(
156
(list(must_fetch), list(if_present_fetch)))
159
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
161
def do_with_branch(self, branch):
162
stacked_on_url = branch.get_stacked_on_url()
163
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
166
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
168
def do_with_branch(self, branch):
169
"""Get the revision history for the branch.
171
The revision list is returned as the body content,
172
with each revision utf8 encoded and \x00 joined.
174
return SuccessfulSmartServerResponse(
175
('ok', ), ('\x00'.join(branch.revision_history())))
178
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
180
def do_with_branch(self, branch):
181
"""Return branch.last_revision_info().
183
The revno is encoded in decimal, the revision_id is encoded as utf8.
185
revno, last_revision = branch.last_revision_info()
186
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
189
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
190
"""Base class for handling common branch request logic for requests that
191
update the branch tip.
194
def do_with_locked_branch(self, branch, *args):
196
return self.do_tip_change_with_locked_branch(branch, *args)
197
except errors.TipChangeRejected, e:
199
if isinstance(msg, unicode):
200
msg = msg.encode('utf-8')
201
return FailedSmartServerResponse(('TipChangeRejected', msg))
204
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
205
"""Set an option in the branch configuration."""
207
def do_with_locked_branch(self, branch, value, name, section):
210
branch._get_config().set_option(value.decode('utf8'), name, section)
211
return SuccessfulSmartServerResponse(())
214
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
215
"""Set an option in the branch configuration.
220
def do_with_locked_branch(self, branch, value_dict, name, section):
221
utf8_dict = bencode.bdecode(value_dict)
223
for key, value in utf8_dict.items():
224
value_dict[key.decode('utf8')] = value.decode('utf8')
227
branch._get_config().set_option(value_dict, name, section)
228
return SuccessfulSmartServerResponse(())
231
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
233
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
234
if new_last_revision_id == 'null:':
235
branch._set_revision_history([])
237
if not branch.repository.has_revision(new_last_revision_id):
238
return FailedSmartServerResponse(
239
('NoSuchRevision', new_last_revision_id))
240
branch._set_revision_history(branch._lefthand_history(
241
new_last_revision_id, None, None))
242
return SuccessfulSmartServerResponse(('ok',))
245
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
247
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
248
allow_divergence, allow_overwrite_descendant):
249
"""Set the last revision of the branch.
253
:param new_last_revision_id: the revision ID to set as the last
254
revision of the branch.
255
:param allow_divergence: A flag. If non-zero, change the revision ID
256
even if the new_last_revision_id's ancestry has diverged from the
257
current last revision. If zero, a 'Diverged' error will be
258
returned if new_last_revision_id is not a descendant of the current
260
:param allow_overwrite_descendant: A flag. If zero and
261
new_last_revision_id is not a descendant of the current last
262
revision, then the last revision will not be changed. If non-zero
263
and there is no divergence, then the last revision is always
266
:returns: on success, a tuple of ('ok', revno, revision_id), where
267
revno and revision_id are the new values of the current last
268
revision info. The revision_id might be different to the
269
new_last_revision_id if allow_overwrite_descendant was not set.
271
do_not_overwrite_descendant = not allow_overwrite_descendant
273
last_revno, last_rev = branch.last_revision_info()
274
graph = branch.repository.get_graph()
275
if not allow_divergence or do_not_overwrite_descendant:
276
relation = branch._revision_relations(
277
last_rev, new_last_revision_id, graph)
278
if relation == 'diverged' and not allow_divergence:
279
return FailedSmartServerResponse(('Diverged',))
280
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
281
return SuccessfulSmartServerResponse(
282
('ok', last_revno, last_rev))
283
new_revno = graph.find_distance_to_null(
284
new_last_revision_id, [(last_rev, last_revno)])
285
branch.set_last_revision_info(new_revno, new_last_revision_id)
286
except errors.GhostRevisionsHaveNoRevno:
287
return FailedSmartServerResponse(
288
('NoSuchRevision', new_last_revision_id))
289
return SuccessfulSmartServerResponse(
290
('ok', new_revno, new_last_revision_id))
293
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
294
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
295
the specified branch.
300
def do_tip_change_with_locked_branch(self, branch, new_revno,
301
new_last_revision_id):
303
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
304
except errors.NoSuchRevision:
305
return FailedSmartServerResponse(
306
('NoSuchRevision', new_last_revision_id))
307
return SuccessfulSmartServerResponse(('ok',))
310
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
311
"""Set the parent location for a branch.
313
Takes a location to set, which must be utf8 encoded.
316
def do_with_locked_branch(self, branch, location):
317
branch._set_parent_location(location)
318
return SuccessfulSmartServerResponse(())
321
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
323
def do_with_branch(self, branch, branch_token='', repo_token=''):
324
if branch_token == '':
329
repo_token = branch.repository.lock_write(
330
token=repo_token).repository_token
332
branch_token = branch.lock_write(
333
token=branch_token).branch_token
335
# this leaves the repository with 1 lock
336
branch.repository.unlock()
337
except errors.LockContention:
338
return FailedSmartServerResponse(('LockContention',))
339
except errors.TokenMismatch:
340
return FailedSmartServerResponse(('TokenMismatch',))
341
except errors.UnlockableTransport:
342
return FailedSmartServerResponse(('UnlockableTransport',))
343
except errors.LockFailed, e:
344
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
345
if repo_token is None:
348
branch.repository.leave_lock_in_place()
349
branch.leave_lock_in_place()
351
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
354
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
356
def do_with_branch(self, branch, branch_token, repo_token):
358
branch.repository.lock_write(token=repo_token)
360
branch.lock_write(token=branch_token)
362
branch.repository.unlock()
363
except errors.TokenMismatch:
364
return FailedSmartServerResponse(('TokenMismatch',))
366
branch.repository.dont_leave_lock_in_place()
367
branch.dont_leave_lock_in_place()
369
return SuccessfulSmartServerResponse(('ok',))