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 SmartServerBranchBreakLock(SmartServerBranchRequest):
82
def do_with_branch(self, branch):
83
"""Break a branch lock.
86
return SuccessfulSmartServerResponse(('ok', ), )
89
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
91
def do_with_branch(self, branch):
92
"""Return the content of branch.conf
94
The body is not utf8 decoded - its the literal bytestream from disk.
97
content = branch.control_transport.get_bytes('branch.conf')
98
except errors.NoSuchFile:
100
return SuccessfulSmartServerResponse( ('ok', ), content)
103
class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
104
"""Set the configuration data for a branch.
109
def do_with_branch(self, branch, branch_token, repo_token):
110
"""Set the content of branch.conf.
112
The body is not utf8 decoded - its the literal bytestream for disk.
114
self._branch = branch
115
self._branch_token = branch_token
116
self._repo_token = repo_token
117
# Signal we want a body
120
def do_body(self, body_bytes):
121
self._branch.repository.lock_write(token=self._repo_token)
123
self._branch.lock_write(token=self._branch_token)
125
self._branch.control_transport.put_bytes(
126
'branch.conf', body_bytes)
128
self._branch.unlock()
130
self._branch.repository.unlock()
131
return SuccessfulSmartServerResponse(('ok', ))
134
class SmartServerBranchGetParent(SmartServerBranchRequest):
136
def do_with_branch(self, branch):
137
"""Return the parent of branch."""
138
parent = branch._get_parent_location() or ''
139
return SuccessfulSmartServerResponse((parent,))
142
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
144
def do_with_branch(self, branch):
145
"""Return the _get_tags_bytes for a branch."""
146
bytes = branch._get_tags_bytes()
147
return SuccessfulSmartServerResponse((bytes,))
150
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
152
def __init__(self, backing_transport, root_client_path='/', jail_root=None):
153
SmartServerLockedBranchRequest.__init__(
154
self, backing_transport, root_client_path, jail_root)
157
def do_with_locked_branch(self, branch):
158
"""Call _set_tags_bytes for a branch.
162
# We need to keep this branch locked until we get a body with the tags
165
self.branch.lock_write()
168
def do_body(self, bytes):
169
self.branch._set_tags_bytes(bytes)
170
return SuccessfulSmartServerResponse(())
173
# TODO: this request shouldn't have to do this housekeeping manually.
174
# Some of this logic probably belongs in a base class.
176
# We never acquired the branch successfully in the first place, so
177
# there's nothing more to do.
180
return SmartServerLockedBranchRequest.do_end(self)
182
# Only try unlocking if we locked successfully in the first place
186
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
188
def do_with_branch(self, branch):
189
"""Return the heads-to-fetch for a Branch as two bencoded lists.
191
See Branch.heads_to_fetch.
195
must_fetch, if_present_fetch = branch.heads_to_fetch()
196
return SuccessfulSmartServerResponse(
197
(list(must_fetch), list(if_present_fetch)))
200
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
202
def do_with_branch(self, branch):
203
stacked_on_url = branch.get_stacked_on_url()
204
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
207
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
209
def do_with_branch(self, branch):
210
"""Get the revision history for the branch.
212
The revision list is returned as the body content,
213
with each revision utf8 encoded and \x00 joined.
217
graph = branch.repository.get_graph()
218
stop_revisions = (None, _mod_revision.NULL_REVISION)
219
history = list(graph.iter_lefthand_ancestry(
220
branch.last_revision(), stop_revisions))
223
return SuccessfulSmartServerResponse(
224
('ok', ), ('\x00'.join(reversed(history))))
227
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
229
def do_with_branch(self, branch):
230
"""Return branch.last_revision_info().
232
The revno is encoded in decimal, the revision_id is encoded as utf8.
234
revno, last_revision = branch.last_revision_info()
235
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
238
class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest):
240
def do_with_branch(self, branch, revid):
241
"""Return branch.revision_id_to_revno().
245
The revno is encoded in decimal, the revision_id is encoded as utf8.
248
dotted_revno = branch.revision_id_to_dotted_revno(revid)
249
except errors.NoSuchRevision:
250
return FailedSmartServerResponse(('NoSuchRevision', revid))
251
return SuccessfulSmartServerResponse(
252
('ok', ) + tuple(map(str, dotted_revno)))
255
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
256
"""Base class for handling common branch request logic for requests that
257
update the branch tip.
260
def do_with_locked_branch(self, branch, *args):
262
return self.do_tip_change_with_locked_branch(branch, *args)
263
except errors.TipChangeRejected, e:
265
if isinstance(msg, unicode):
266
msg = msg.encode('utf-8')
267
return FailedSmartServerResponse(('TipChangeRejected', msg))
270
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
271
"""Set an option in the branch configuration."""
273
def do_with_locked_branch(self, branch, value, name, section):
276
branch._get_config().set_option(value.decode('utf8'), name, section)
277
return SuccessfulSmartServerResponse(())
280
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
281
"""Set an option in the branch configuration.
286
def do_with_locked_branch(self, branch, value_dict, name, section):
287
utf8_dict = bencode.bdecode(value_dict)
289
for key, value in utf8_dict.items():
290
value_dict[key.decode('utf8')] = value.decode('utf8')
293
branch._get_config().set_option(value_dict, name, section)
294
return SuccessfulSmartServerResponse(())
297
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
299
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
300
if new_last_revision_id == 'null:':
301
branch._set_revision_history([])
303
if not branch.repository.has_revision(new_last_revision_id):
304
return FailedSmartServerResponse(
305
('NoSuchRevision', new_last_revision_id))
306
branch._set_revision_history(branch._lefthand_history(
307
new_last_revision_id, None, None))
308
return SuccessfulSmartServerResponse(('ok',))
311
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
313
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
314
allow_divergence, allow_overwrite_descendant):
315
"""Set the last revision of the branch.
319
:param new_last_revision_id: the revision ID to set as the last
320
revision of the branch.
321
:param allow_divergence: A flag. If non-zero, change the revision ID
322
even if the new_last_revision_id's ancestry has diverged from the
323
current last revision. If zero, a 'Diverged' error will be
324
returned if new_last_revision_id is not a descendant of the current
326
:param allow_overwrite_descendant: A flag. If zero and
327
new_last_revision_id is not a descendant of the current last
328
revision, then the last revision will not be changed. If non-zero
329
and there is no divergence, then the last revision is always
332
:returns: on success, a tuple of ('ok', revno, revision_id), where
333
revno and revision_id are the new values of the current last
334
revision info. The revision_id might be different to the
335
new_last_revision_id if allow_overwrite_descendant was not set.
337
do_not_overwrite_descendant = not allow_overwrite_descendant
339
last_revno, last_rev = branch.last_revision_info()
340
graph = branch.repository.get_graph()
341
if not allow_divergence or do_not_overwrite_descendant:
342
relation = branch._revision_relations(
343
last_rev, new_last_revision_id, graph)
344
if relation == 'diverged' and not allow_divergence:
345
return FailedSmartServerResponse(('Diverged',))
346
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
347
return SuccessfulSmartServerResponse(
348
('ok', last_revno, last_rev))
349
new_revno = graph.find_distance_to_null(
350
new_last_revision_id, [(last_rev, last_revno)])
351
branch.set_last_revision_info(new_revno, new_last_revision_id)
352
except errors.GhostRevisionsHaveNoRevno:
353
return FailedSmartServerResponse(
354
('NoSuchRevision', new_last_revision_id))
355
return SuccessfulSmartServerResponse(
356
('ok', new_revno, new_last_revision_id))
359
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
360
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
361
the specified branch.
366
def do_tip_change_with_locked_branch(self, branch, new_revno,
367
new_last_revision_id):
369
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
370
except errors.NoSuchRevision:
371
return FailedSmartServerResponse(
372
('NoSuchRevision', new_last_revision_id))
373
return SuccessfulSmartServerResponse(('ok',))
376
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
377
"""Set the parent location for a branch.
379
Takes a location to set, which must be utf8 encoded.
382
def do_with_locked_branch(self, branch, location):
383
branch._set_parent_location(location)
384
return SuccessfulSmartServerResponse(())
387
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
389
def do_with_branch(self, branch, branch_token='', repo_token=''):
390
if branch_token == '':
395
repo_token = branch.repository.lock_write(
396
token=repo_token).repository_token
398
branch_token = branch.lock_write(
399
token=branch_token).branch_token
401
# this leaves the repository with 1 lock
402
branch.repository.unlock()
403
except errors.LockContention:
404
return FailedSmartServerResponse(('LockContention',))
405
except errors.TokenMismatch:
406
return FailedSmartServerResponse(('TokenMismatch',))
407
except errors.UnlockableTransport:
408
return FailedSmartServerResponse(('UnlockableTransport',))
409
except errors.LockFailed, e:
410
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
411
if repo_token is None:
414
branch.repository.leave_lock_in_place()
415
branch.leave_lock_in_place()
417
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
420
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
422
def do_with_branch(self, branch, branch_token, repo_token):
424
branch.repository.lock_write(token=repo_token)
426
branch.lock_write(token=branch_token)
428
branch.repository.unlock()
429
except errors.TokenMismatch:
430
return FailedSmartServerResponse(('TokenMismatch',))
432
branch.repository.dont_leave_lock_in_place()
433
branch.dont_leave_lock_in_place()
435
return SuccessfulSmartServerResponse(('ok',))
438
class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest):
439
"""Get the physical lock status for a branch.
444
def do_with_branch(self, branch):
445
if branch.get_physical_lock_status():
446
return SuccessfulSmartServerResponse(('yes',))
448
return SuccessfulSmartServerResponse(('no',))