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."""
23
revision as _mod_revision,
25
from bzrlib.controldir import ControlDir
26
from bzrlib.smart.request import (
27
FailedSmartServerResponse,
29
SuccessfulSmartServerResponse,
33
class SmartServerBranchRequest(SmartServerRequest):
34
"""Base class for handling common branch request logic.
37
def do(self, path, *args):
38
"""Execute a request for a branch at path.
40
All Branch requests take a path to the branch as their first argument.
42
If the branch is a branch reference, NotBranchError is raised.
44
:param path: The path for the repository as received from the
46
:return: A SmartServerResponse from self.do_with_branch().
48
transport = self.transport_from_client_path(path)
49
controldir = ControlDir.open_from_transport(transport)
50
if controldir.get_branch_reference() is not None:
51
raise errors.NotBranchError(transport.base)
52
branch = controldir.open_branch(ignore_fallbacks=True)
53
return self.do_with_branch(branch, *args)
56
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
57
"""Base class for handling common branch request logic for requests that
61
def do_with_branch(self, branch, branch_token, repo_token, *args):
62
"""Execute a request for a branch.
64
A write lock will be acquired with the given tokens for the branch and
65
repository locks. The lock will be released once the request is
66
processed. The physical lock state won't be changed.
68
# XXX: write a test for LockContention
69
branch.repository.lock_write(token=repo_token)
71
branch.lock_write(token=branch_token)
73
return self.do_with_locked_branch(branch, *args)
77
branch.repository.unlock()
80
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
82
def do_with_branch(self, branch):
83
"""Return the content of branch.conf
85
The body is not utf8 decoded - its the literal bytestream from disk.
88
content = branch._transport.get_bytes('branch.conf')
89
except errors.NoSuchFile:
91
return SuccessfulSmartServerResponse( ('ok', ), content)
94
class SmartServerBranchGetParent(SmartServerBranchRequest):
96
def do_with_branch(self, branch):
97
"""Return the parent of branch."""
98
parent = branch._get_parent_location() or ''
99
return SuccessfulSmartServerResponse((parent,))
102
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
104
def do_with_branch(self, branch):
105
"""Return the _get_tags_bytes for a branch."""
106
bytes = branch._get_tags_bytes()
107
return SuccessfulSmartServerResponse((bytes,))
110
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
112
def __init__(self, backing_transport, root_client_path='/', jail_root=None):
113
SmartServerLockedBranchRequest.__init__(
114
self, backing_transport, root_client_path, jail_root)
117
def do_with_locked_branch(self, branch):
118
"""Call _set_tags_bytes for a branch.
122
# We need to keep this branch locked until we get a body with the tags
125
self.branch.lock_write()
128
def do_body(self, bytes):
129
self.branch._set_tags_bytes(bytes)
130
return SuccessfulSmartServerResponse(())
133
# TODO: this request shouldn't have to do this housekeeping manually.
134
# Some of this logic probably belongs in a base class.
136
# We never acquired the branch successfully in the first place, so
137
# there's nothing more to do.
140
return SmartServerLockedBranchRequest.do_end(self)
142
# Only try unlocking if we locked successfully in the first place
146
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
148
def do_with_branch(self, branch):
149
"""Return the heads-to-fetch for a Branch as two bencoded lists.
151
See Branch.heads_to_fetch.
155
must_fetch, if_present_fetch = branch.heads_to_fetch()
156
return SuccessfulSmartServerResponse(
157
(list(must_fetch), list(if_present_fetch)))
160
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
162
def do_with_branch(self, branch):
163
stacked_on_url = branch.get_stacked_on_url()
164
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
167
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
169
def do_with_branch(self, branch):
170
"""Get the revision history for the branch.
172
The revision list is returned as the body content,
173
with each revision utf8 encoded and \x00 joined.
177
graph = branch.repository.get_graph()
178
stop_revisions = (None, _mod_revision.NULL_REVISION)
179
history = list(graph.iter_lefthand_ancestry(
180
branch.last_revision(), stop_revisions))
183
return SuccessfulSmartServerResponse(
184
('ok', ), ('\x00'.join(reversed(history))))
187
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
189
def do_with_branch(self, branch):
190
"""Return branch.last_revision_info().
192
The revno is encoded in decimal, the revision_id is encoded as utf8.
194
revno, last_revision = branch.last_revision_info()
195
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
198
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
199
"""Base class for handling common branch request logic for requests that
200
update the branch tip.
203
def do_with_locked_branch(self, branch, *args):
205
return self.do_tip_change_with_locked_branch(branch, *args)
206
except errors.TipChangeRejected, e:
208
if isinstance(msg, unicode):
209
msg = msg.encode('utf-8')
210
return FailedSmartServerResponse(('TipChangeRejected', msg))
213
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
214
"""Set an option in the branch configuration."""
216
def do_with_locked_branch(self, branch, value, name, section):
219
branch._get_config().set_option(value.decode('utf8'), name, section)
220
return SuccessfulSmartServerResponse(())
223
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
224
"""Set an option in the branch configuration.
229
def do_with_locked_branch(self, branch, value_dict, name, section):
230
utf8_dict = bencode.bdecode(value_dict)
232
for key, value in utf8_dict.items():
233
value_dict[key.decode('utf8')] = value.decode('utf8')
236
branch._get_config().set_option(value_dict, name, section)
237
return SuccessfulSmartServerResponse(())
240
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
242
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
243
if new_last_revision_id == 'null:':
244
branch._set_revision_history([])
246
if not branch.repository.has_revision(new_last_revision_id):
247
return FailedSmartServerResponse(
248
('NoSuchRevision', new_last_revision_id))
249
branch._set_revision_history(branch._lefthand_history(
250
new_last_revision_id, None, None))
251
return SuccessfulSmartServerResponse(('ok',))
254
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
256
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
257
allow_divergence, allow_overwrite_descendant):
258
"""Set the last revision of the branch.
262
:param new_last_revision_id: the revision ID to set as the last
263
revision of the branch.
264
:param allow_divergence: A flag. If non-zero, change the revision ID
265
even if the new_last_revision_id's ancestry has diverged from the
266
current last revision. If zero, a 'Diverged' error will be
267
returned if new_last_revision_id is not a descendant of the current
269
:param allow_overwrite_descendant: A flag. If zero and
270
new_last_revision_id is not a descendant of the current last
271
revision, then the last revision will not be changed. If non-zero
272
and there is no divergence, then the last revision is always
275
:returns: on success, a tuple of ('ok', revno, revision_id), where
276
revno and revision_id are the new values of the current last
277
revision info. The revision_id might be different to the
278
new_last_revision_id if allow_overwrite_descendant was not set.
280
do_not_overwrite_descendant = not allow_overwrite_descendant
282
last_revno, last_rev = branch.last_revision_info()
283
graph = branch.repository.get_graph()
284
if not allow_divergence or do_not_overwrite_descendant:
285
relation = branch._revision_relations(
286
last_rev, new_last_revision_id, graph)
287
if relation == 'diverged' and not allow_divergence:
288
return FailedSmartServerResponse(('Diverged',))
289
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
290
return SuccessfulSmartServerResponse(
291
('ok', last_revno, last_rev))
292
new_revno = graph.find_distance_to_null(
293
new_last_revision_id, [(last_rev, last_revno)])
294
branch.set_last_revision_info(new_revno, new_last_revision_id)
295
except errors.GhostRevisionsHaveNoRevno:
296
return FailedSmartServerResponse(
297
('NoSuchRevision', new_last_revision_id))
298
return SuccessfulSmartServerResponse(
299
('ok', new_revno, new_last_revision_id))
302
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
303
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
304
the specified branch.
309
def do_tip_change_with_locked_branch(self, branch, new_revno,
310
new_last_revision_id):
312
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
313
except errors.NoSuchRevision:
314
return FailedSmartServerResponse(
315
('NoSuchRevision', new_last_revision_id))
316
return SuccessfulSmartServerResponse(('ok',))
319
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
320
"""Set the parent location for a branch.
322
Takes a location to set, which must be utf8 encoded.
325
def do_with_locked_branch(self, branch, location):
326
branch._set_parent_location(location)
327
return SuccessfulSmartServerResponse(())
330
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
332
def do_with_branch(self, branch, branch_token='', repo_token=''):
333
if branch_token == '':
338
repo_token = branch.repository.lock_write(
339
token=repo_token).repository_token
341
branch_token = branch.lock_write(
342
token=branch_token).branch_token
344
# this leaves the repository with 1 lock
345
branch.repository.unlock()
346
except errors.LockContention:
347
return FailedSmartServerResponse(('LockContention',))
348
except errors.TokenMismatch:
349
return FailedSmartServerResponse(('TokenMismatch',))
350
except errors.UnlockableTransport:
351
return FailedSmartServerResponse(('UnlockableTransport',))
352
except errors.LockFailed, e:
353
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
354
if repo_token is None:
357
branch.repository.leave_lock_in_place()
358
branch.leave_lock_in_place()
360
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
363
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
365
def do_with_branch(self, branch, branch_token, repo_token):
367
branch.repository.lock_write(token=repo_token)
369
branch.lock_write(token=branch_token)
371
branch.repository.unlock()
372
except errors.TokenMismatch:
373
return FailedSmartServerResponse(('TokenMismatch',))
375
branch.repository.dont_leave_lock_in_place()
376
branch.dont_leave_lock_in_place()
378
return SuccessfulSmartServerResponse(('ok',))