1
# Copyright (C) 2006 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."""
20
from bzrlib import errors
21
from bzrlib.bzrdir import BzrDir
22
from bzrlib.smart.request import (
23
FailedSmartServerResponse,
25
SuccessfulSmartServerResponse,
29
class SmartServerBranchRequest(SmartServerRequest):
30
"""Base class for handling common branch request logic.
33
def do(self, path, *args):
34
"""Execute a request for a branch at path.
36
All Branch requests take a path to the branch as their first argument.
38
If the branch is a branch reference, NotBranchError is raised.
40
:param path: The path for the repository as received from the
42
:return: A SmartServerResponse from self.do_with_branch().
44
transport = self.transport_from_client_path(path)
45
bzrdir = BzrDir.open_from_transport(transport)
46
if bzrdir.get_branch_reference() is not None:
47
raise errors.NotBranchError(transport.base)
48
branch = bzrdir.open_branch(ignore_fallbacks=True)
49
return self.do_with_branch(branch, *args)
52
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
53
"""Base class for handling common branch request logic for requests that
57
def do_with_branch(self, branch, branch_token, repo_token, *args):
58
"""Execute a request for a branch.
60
A write lock will be acquired with the given tokens for the branch and
61
repository locks. The lock will be released once the request is
62
processed. The physical lock state won't be changed.
64
# XXX: write a test for LockContention
65
branch.repository.lock_write(token=repo_token)
67
branch.lock_write(token=branch_token)
69
return self.do_with_locked_branch(branch, *args)
73
branch.repository.unlock()
76
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
78
def do_with_branch(self, branch):
79
"""Return the content of branch.conf
81
The body is not utf8 decoded - its the literal bytestream from disk.
84
content = branch._transport.get_bytes('branch.conf')
85
except errors.NoSuchFile:
87
return SuccessfulSmartServerResponse( ('ok', ), content)
90
class SmartServerBranchGetParent(SmartServerBranchRequest):
92
def do_with_branch(self, branch):
93
"""Return the parent of branch."""
94
parent = branch._get_parent_location() or ''
95
return SuccessfulSmartServerResponse((parent,))
98
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
100
def do_with_branch(self, branch):
101
"""Return the _get_tags_bytes for a branch."""
102
bytes = branch._get_tags_bytes()
103
return SuccessfulSmartServerResponse((bytes,))
106
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
108
def __init__(self, backing_transport, root_client_path='/'):
109
SmartServerLockedBranchRequest.__init__(
110
self, backing_transport, root_client_path)
113
def do_with_locked_branch(self, branch):
114
# We need to keep this branch locked until we get a body with the tags
117
self.branch.lock_write()
120
def do_body(self, bytes):
121
self.branch._set_tags_bytes(bytes)
122
return SuccessfulSmartServerResponse(())
126
# We never acquired the branch successfully in the first place, so
127
# there's nothing more to do.
128
# XXX: perhaps it should be easier for request classes to do
129
# nothing further if they have raised an error previously? It
130
# seems a bit messy to require them to keep track of this state.
133
return SmartServerLockedBranchRequest.do_end(self)
135
# Only try unlocking if we locked successfully in the first place
139
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
141
def do_with_branch(self, branch):
142
stacked_on_url = branch.get_stacked_on_url()
143
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
146
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
148
def do_with_branch(self, branch):
149
"""Get the revision history for the branch.
151
The revision list is returned as the body content,
152
with each revision utf8 encoded and \x00 joined.
154
return SuccessfulSmartServerResponse(
155
('ok', ), ('\x00'.join(branch.revision_history())))
158
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
160
def do_with_branch(self, branch):
161
"""Return branch.last_revision_info().
163
The revno is encoded in decimal, the revision_id is encoded as utf8.
165
revno, last_revision = branch.last_revision_info()
166
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
169
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
170
"""Base class for handling common branch request logic for requests that
171
update the branch tip.
174
def do_with_locked_branch(self, branch, *args):
176
return self.do_tip_change_with_locked_branch(branch, *args)
177
except errors.TipChangeRejected, e:
179
if isinstance(msg, unicode):
180
msg = msg.encode('utf-8')
181
return FailedSmartServerResponse(('TipChangeRejected', msg))
184
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
185
"""Set an option in the branch configuration."""
187
def do_with_locked_branch(self, branch, value, name, section):
190
branch._get_config().set_option(value.decode('utf8'), name, section)
191
return SuccessfulSmartServerResponse(())
194
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
196
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
197
if new_last_revision_id == 'null:':
198
branch.set_revision_history([])
200
if not branch.repository.has_revision(new_last_revision_id):
201
return FailedSmartServerResponse(
202
('NoSuchRevision', new_last_revision_id))
203
branch.set_revision_history(branch._lefthand_history(
204
new_last_revision_id, None, None))
205
return SuccessfulSmartServerResponse(('ok',))
208
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
210
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
211
allow_divergence, allow_overwrite_descendant):
212
"""Set the last revision of the branch.
216
:param new_last_revision_id: the revision ID to set as the last
217
revision of the branch.
218
:param allow_divergence: A flag. If non-zero, change the revision ID
219
even if the new_last_revision_id's ancestry has diverged from the
220
current last revision. If zero, a 'Diverged' error will be
221
returned if new_last_revision_id is not a descendant of the current
223
:param allow_overwrite_descendant: A flag. If zero and
224
new_last_revision_id is not a descendant of the current last
225
revision, then the last revision will not be changed. If non-zero
226
and there is no divergence, then the last revision is always
229
:returns: on success, a tuple of ('ok', revno, revision_id), where
230
revno and revision_id are the new values of the current last
231
revision info. The revision_id might be different to the
232
new_last_revision_id if allow_overwrite_descendant was not set.
234
do_not_overwrite_descendant = not allow_overwrite_descendant
236
last_revno, last_rev = branch.last_revision_info()
237
graph = branch.repository.get_graph()
238
if not allow_divergence or do_not_overwrite_descendant:
239
relation = branch._revision_relations(
240
last_rev, new_last_revision_id, graph)
241
if relation == 'diverged' and not allow_divergence:
242
return FailedSmartServerResponse(('Diverged',))
243
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
244
return SuccessfulSmartServerResponse(
245
('ok', last_revno, last_rev))
246
new_revno = graph.find_distance_to_null(
247
new_last_revision_id, [(last_rev, last_revno)])
248
branch.set_last_revision_info(new_revno, new_last_revision_id)
249
except errors.GhostRevisionsHaveNoRevno:
250
return FailedSmartServerResponse(
251
('NoSuchRevision', new_last_revision_id))
252
return SuccessfulSmartServerResponse(
253
('ok', new_revno, new_last_revision_id))
256
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
257
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
258
the specified branch.
263
def do_tip_change_with_locked_branch(self, branch, new_revno,
264
new_last_revision_id):
266
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
267
except errors.NoSuchRevision:
268
return FailedSmartServerResponse(
269
('NoSuchRevision', new_last_revision_id))
270
return SuccessfulSmartServerResponse(('ok',))
273
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
274
"""Set the parent location for a branch.
276
Takes a location to set, which must be utf8 encoded.
279
def do_with_locked_branch(self, branch, location):
280
branch._set_parent_location(location)
281
return SuccessfulSmartServerResponse(())
284
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
286
def do_with_branch(self, branch, branch_token='', repo_token=''):
287
if branch_token == '':
292
repo_token = branch.repository.lock_write(token=repo_token)
294
branch_token = branch.lock_write(token=branch_token)
296
# this leaves the repository with 1 lock
297
branch.repository.unlock()
298
except errors.LockContention:
299
return FailedSmartServerResponse(('LockContention',))
300
except errors.TokenMismatch:
301
return FailedSmartServerResponse(('TokenMismatch',))
302
except errors.UnlockableTransport:
303
return FailedSmartServerResponse(('UnlockableTransport',))
304
except errors.LockFailed, e:
305
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
306
if repo_token is None:
309
branch.repository.leave_lock_in_place()
310
branch.leave_lock_in_place()
312
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
315
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
317
def do_with_branch(self, branch, branch_token, repo_token):
319
branch.repository.lock_write(token=repo_token)
321
branch.lock_write(token=branch_token)
323
branch.repository.unlock()
324
except errors.TokenMismatch:
325
return FailedSmartServerResponse(('TokenMismatch',))
327
branch.repository.dont_leave_lock_in_place()
328
branch.dont_leave_lock_in_place()
330
return SuccessfulSmartServerResponse(('ok',))