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
"""Call _set_tags_bytes for a branch.
118
# We need to keep this branch locked until we get a body with the tags
121
self.branch.lock_write()
124
def do_body(self, bytes):
125
self.branch._set_tags_bytes(bytes)
126
return SuccessfulSmartServerResponse(())
129
# TODO: this request shouldn't have to do this housekeeping manually.
130
# Some of this logic probably belongs in a base class.
132
# We never acquired the branch successfully in the first place, so
133
# there's nothing more to do.
136
return SmartServerLockedBranchRequest.do_end(self)
138
# Only try unlocking if we locked successfully in the first place
142
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
144
def do_with_branch(self, branch):
145
stacked_on_url = branch.get_stacked_on_url()
146
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
149
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
151
def do_with_branch(self, branch):
152
"""Get the revision history for the branch.
154
The revision list is returned as the body content,
155
with each revision utf8 encoded and \x00 joined.
157
return SuccessfulSmartServerResponse(
158
('ok', ), ('\x00'.join(branch.revision_history())))
161
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
163
def do_with_branch(self, branch):
164
"""Return branch.last_revision_info().
166
The revno is encoded in decimal, the revision_id is encoded as utf8.
168
revno, last_revision = branch.last_revision_info()
169
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
172
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
173
"""Base class for handling common branch request logic for requests that
174
update the branch tip.
177
def do_with_locked_branch(self, branch, *args):
179
return self.do_tip_change_with_locked_branch(branch, *args)
180
except errors.TipChangeRejected, e:
182
if isinstance(msg, unicode):
183
msg = msg.encode('utf-8')
184
return FailedSmartServerResponse(('TipChangeRejected', msg))
187
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
188
"""Set an option in the branch configuration."""
190
def do_with_locked_branch(self, branch, value, name, section):
193
branch._get_config().set_option(value.decode('utf8'), name, section)
194
return SuccessfulSmartServerResponse(())
197
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
199
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
200
if new_last_revision_id == 'null:':
201
branch.set_revision_history([])
203
if not branch.repository.has_revision(new_last_revision_id):
204
return FailedSmartServerResponse(
205
('NoSuchRevision', new_last_revision_id))
206
branch.set_revision_history(branch._lefthand_history(
207
new_last_revision_id, None, None))
208
return SuccessfulSmartServerResponse(('ok',))
211
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
213
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
214
allow_divergence, allow_overwrite_descendant):
215
"""Set the last revision of the branch.
219
:param new_last_revision_id: the revision ID to set as the last
220
revision of the branch.
221
:param allow_divergence: A flag. If non-zero, change the revision ID
222
even if the new_last_revision_id's ancestry has diverged from the
223
current last revision. If zero, a 'Diverged' error will be
224
returned if new_last_revision_id is not a descendant of the current
226
:param allow_overwrite_descendant: A flag. If zero and
227
new_last_revision_id is not a descendant of the current last
228
revision, then the last revision will not be changed. If non-zero
229
and there is no divergence, then the last revision is always
232
:returns: on success, a tuple of ('ok', revno, revision_id), where
233
revno and revision_id are the new values of the current last
234
revision info. The revision_id might be different to the
235
new_last_revision_id if allow_overwrite_descendant was not set.
237
do_not_overwrite_descendant = not allow_overwrite_descendant
239
last_revno, last_rev = branch.last_revision_info()
240
graph = branch.repository.get_graph()
241
if not allow_divergence or do_not_overwrite_descendant:
242
relation = branch._revision_relations(
243
last_rev, new_last_revision_id, graph)
244
if relation == 'diverged' and not allow_divergence:
245
return FailedSmartServerResponse(('Diverged',))
246
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
247
return SuccessfulSmartServerResponse(
248
('ok', last_revno, last_rev))
249
new_revno = graph.find_distance_to_null(
250
new_last_revision_id, [(last_rev, last_revno)])
251
branch.set_last_revision_info(new_revno, new_last_revision_id)
252
except errors.GhostRevisionsHaveNoRevno:
253
return FailedSmartServerResponse(
254
('NoSuchRevision', new_last_revision_id))
255
return SuccessfulSmartServerResponse(
256
('ok', new_revno, new_last_revision_id))
259
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
260
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
261
the specified branch.
266
def do_tip_change_with_locked_branch(self, branch, new_revno,
267
new_last_revision_id):
269
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
270
except errors.NoSuchRevision:
271
return FailedSmartServerResponse(
272
('NoSuchRevision', new_last_revision_id))
273
return SuccessfulSmartServerResponse(('ok',))
276
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
277
"""Set the parent location for a branch.
279
Takes a location to set, which must be utf8 encoded.
282
def do_with_locked_branch(self, branch, location):
283
branch._set_parent_location(location)
284
return SuccessfulSmartServerResponse(())
287
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
289
def do_with_branch(self, branch, branch_token='', repo_token=''):
290
if branch_token == '':
295
repo_token = branch.repository.lock_write(token=repo_token)
297
branch_token = branch.lock_write(token=branch_token)
299
# this leaves the repository with 1 lock
300
branch.repository.unlock()
301
except errors.LockContention:
302
return FailedSmartServerResponse(('LockContention',))
303
except errors.TokenMismatch:
304
return FailedSmartServerResponse(('TokenMismatch',))
305
except errors.UnlockableTransport:
306
return FailedSmartServerResponse(('UnlockableTransport',))
307
except errors.LockFailed, e:
308
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
309
if repo_token is None:
312
branch.repository.leave_lock_in_place()
313
branch.leave_lock_in_place()
315
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
318
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
320
def do_with_branch(self, branch, branch_token, repo_token):
322
branch.repository.lock_write(token=repo_token)
324
branch.lock_write(token=branch_token)
326
branch.repository.unlock()
327
except errors.TokenMismatch:
328
return FailedSmartServerResponse(('TokenMismatch',))
330
branch.repository.dont_leave_lock_in_place()
331
branch.dont_leave_lock_in_place()
333
return SuccessfulSmartServerResponse(('ok',))