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."""
19
from __future__ import absolute_import
24
revision as _mod_revision,
26
from bzrlib.controldir import ControlDir
27
from bzrlib.smart.request import (
28
FailedSmartServerResponse,
30
SuccessfulSmartServerResponse,
34
class SmartServerBranchRequest(SmartServerRequest):
35
"""Base class for handling common branch request logic.
38
def do(self, path, *args):
39
"""Execute a request for a branch at path.
41
All Branch requests take a path to the branch as their first argument.
43
If the branch is a branch reference, NotBranchError is raised.
45
:param path: The path for the repository as received from the
47
:return: A SmartServerResponse from self.do_with_branch().
49
transport = self.transport_from_client_path(path)
50
controldir = ControlDir.open_from_transport(transport)
51
if controldir.get_branch_reference() is not None:
52
raise errors.NotBranchError(transport.base)
53
branch = controldir.open_branch(ignore_fallbacks=True)
54
return self.do_with_branch(branch, *args)
57
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
58
"""Base class for handling common branch request logic for requests that
62
def do_with_branch(self, branch, branch_token, repo_token, *args):
63
"""Execute a request for a branch.
65
A write lock will be acquired with the given tokens for the branch and
66
repository locks. The lock will be released once the request is
67
processed. The physical lock state won't be changed.
69
# XXX: write a test for LockContention
70
branch.repository.lock_write(token=repo_token)
72
branch.lock_write(token=branch_token)
74
return self.do_with_locked_branch(branch, *args)
78
branch.repository.unlock()
81
class SmartServerBranchBreakLock(SmartServerBranchRequest):
83
def do_with_branch(self, branch):
84
"""Break a branch lock.
87
return SuccessfulSmartServerResponse(('ok', ), )
90
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
92
def do_with_branch(self, branch):
93
"""Return the content of branch.conf
95
The body is not utf8 decoded - its the literal bytestream from disk.
98
content = branch.control_transport.get_bytes('branch.conf')
99
except errors.NoSuchFile:
101
return SuccessfulSmartServerResponse( ('ok', ), content)
104
class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
105
"""Set the configuration data for a branch.
110
def do_with_branch(self, branch, branch_token, repo_token):
111
"""Set the content of branch.conf.
113
The body is not utf8 decoded - its the literal bytestream for disk.
115
self._branch = branch
116
self._branch_token = branch_token
117
self._repo_token = repo_token
118
# Signal we want a body
121
def do_body(self, body_bytes):
122
self._branch.repository.lock_write(token=self._repo_token)
124
self._branch.lock_write(token=self._branch_token)
126
self._branch.control_transport.put_bytes(
127
'branch.conf', body_bytes)
129
self._branch.unlock()
131
self._branch.repository.unlock()
132
return SuccessfulSmartServerResponse(('ok', ))
135
class SmartServerBranchGetParent(SmartServerBranchRequest):
137
def do_with_branch(self, branch):
138
"""Return the parent of branch."""
139
parent = branch._get_parent_location() or ''
140
return SuccessfulSmartServerResponse((parent,))
143
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
145
def do_with_branch(self, branch):
146
"""Return the _get_tags_bytes for a branch."""
147
bytes = branch._get_tags_bytes()
148
return SuccessfulSmartServerResponse((bytes,))
151
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
153
def __init__(self, backing_transport, root_client_path='/', jail_root=None):
154
SmartServerLockedBranchRequest.__init__(
155
self, backing_transport, root_client_path, jail_root)
158
def do_with_locked_branch(self, branch):
159
"""Call _set_tags_bytes for a branch.
163
# We need to keep this branch locked until we get a body with the tags
166
self.branch.lock_write()
169
def do_body(self, bytes):
170
self.branch._set_tags_bytes(bytes)
171
return SuccessfulSmartServerResponse(())
174
# TODO: this request shouldn't have to do this housekeeping manually.
175
# Some of this logic probably belongs in a base class.
177
# We never acquired the branch successfully in the first place, so
178
# there's nothing more to do.
181
return SmartServerLockedBranchRequest.do_end(self)
183
# Only try unlocking if we locked successfully in the first place
187
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
189
def do_with_branch(self, branch):
190
"""Return the heads-to-fetch for a Branch as two bencoded lists.
192
See Branch.heads_to_fetch.
196
must_fetch, if_present_fetch = branch.heads_to_fetch()
197
return SuccessfulSmartServerResponse(
198
(list(must_fetch), list(if_present_fetch)))
201
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
203
def do_with_branch(self, branch):
204
stacked_on_url = branch.get_stacked_on_url()
205
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
208
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
210
def do_with_branch(self, branch):
211
"""Get the revision history for the branch.
213
The revision list is returned as the body content,
214
with each revision utf8 encoded and \x00 joined.
218
graph = branch.repository.get_graph()
219
stop_revisions = (None, _mod_revision.NULL_REVISION)
220
history = list(graph.iter_lefthand_ancestry(
221
branch.last_revision(), stop_revisions))
224
return SuccessfulSmartServerResponse(
225
('ok', ), ('\x00'.join(reversed(history))))
228
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
230
def do_with_branch(self, branch):
231
"""Return branch.last_revision_info().
233
The revno is encoded in decimal, the revision_id is encoded as utf8.
235
revno, last_revision = branch.last_revision_info()
236
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
239
class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest):
241
def do_with_branch(self, branch, revid):
242
"""Return branch.revision_id_to_revno().
246
The revno is encoded in decimal, the revision_id is encoded as utf8.
249
dotted_revno = branch.revision_id_to_dotted_revno(revid)
250
except errors.NoSuchRevision:
251
return FailedSmartServerResponse(('NoSuchRevision', revid))
252
return SuccessfulSmartServerResponse(
253
('ok', ) + tuple(map(str, dotted_revno)))
256
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
257
"""Base class for handling common branch request logic for requests that
258
update the branch tip.
261
def do_with_locked_branch(self, branch, *args):
263
return self.do_tip_change_with_locked_branch(branch, *args)
264
except errors.TipChangeRejected, e:
266
if isinstance(msg, unicode):
267
msg = msg.encode('utf-8')
268
return FailedSmartServerResponse(('TipChangeRejected', msg))
271
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
272
"""Set an option in the branch configuration."""
274
def do_with_locked_branch(self, branch, value, name, section):
277
branch._get_config().set_option(value.decode('utf8'), name, section)
278
return SuccessfulSmartServerResponse(())
281
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
282
"""Set an option in the branch configuration.
287
def do_with_locked_branch(self, branch, value_dict, name, section):
288
utf8_dict = bencode.bdecode(value_dict)
290
for key, value in utf8_dict.items():
291
value_dict[key.decode('utf8')] = value.decode('utf8')
294
branch._get_config().set_option(value_dict, name, section)
295
return SuccessfulSmartServerResponse(())
298
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
300
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
301
if new_last_revision_id == 'null:':
302
branch._set_revision_history([])
304
if not branch.repository.has_revision(new_last_revision_id):
305
return FailedSmartServerResponse(
306
('NoSuchRevision', new_last_revision_id))
307
branch._set_revision_history(branch._lefthand_history(
308
new_last_revision_id, None, None))
309
return SuccessfulSmartServerResponse(('ok',))
312
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
314
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
315
allow_divergence, allow_overwrite_descendant):
316
"""Set the last revision of the branch.
320
:param new_last_revision_id: the revision ID to set as the last
321
revision of the branch.
322
:param allow_divergence: A flag. If non-zero, change the revision ID
323
even if the new_last_revision_id's ancestry has diverged from the
324
current last revision. If zero, a 'Diverged' error will be
325
returned if new_last_revision_id is not a descendant of the current
327
:param allow_overwrite_descendant: A flag. If zero and
328
new_last_revision_id is not a descendant of the current last
329
revision, then the last revision will not be changed. If non-zero
330
and there is no divergence, then the last revision is always
333
:returns: on success, a tuple of ('ok', revno, revision_id), where
334
revno and revision_id are the new values of the current last
335
revision info. The revision_id might be different to the
336
new_last_revision_id if allow_overwrite_descendant was not set.
338
do_not_overwrite_descendant = not allow_overwrite_descendant
340
last_revno, last_rev = branch.last_revision_info()
341
graph = branch.repository.get_graph()
342
if not allow_divergence or do_not_overwrite_descendant:
343
relation = branch._revision_relations(
344
last_rev, new_last_revision_id, graph)
345
if relation == 'diverged' and not allow_divergence:
346
return FailedSmartServerResponse(('Diverged',))
347
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
348
return SuccessfulSmartServerResponse(
349
('ok', last_revno, last_rev))
350
new_revno = graph.find_distance_to_null(
351
new_last_revision_id, [(last_rev, last_revno)])
352
branch.set_last_revision_info(new_revno, new_last_revision_id)
353
except errors.GhostRevisionsHaveNoRevno:
354
return FailedSmartServerResponse(
355
('NoSuchRevision', new_last_revision_id))
356
return SuccessfulSmartServerResponse(
357
('ok', new_revno, new_last_revision_id))
360
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
361
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
362
the specified branch.
367
def do_tip_change_with_locked_branch(self, branch, new_revno,
368
new_last_revision_id):
370
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
371
except errors.NoSuchRevision:
372
return FailedSmartServerResponse(
373
('NoSuchRevision', new_last_revision_id))
374
return SuccessfulSmartServerResponse(('ok',))
377
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
378
"""Set the parent location for a branch.
380
Takes a location to set, which must be utf8 encoded.
383
def do_with_locked_branch(self, branch, location):
384
branch._set_parent_location(location)
385
return SuccessfulSmartServerResponse(())
388
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
390
def do_with_branch(self, branch, branch_token='', repo_token=''):
391
if branch_token == '':
396
repo_token = branch.repository.lock_write(
397
token=repo_token).repository_token
399
branch_token = branch.lock_write(
400
token=branch_token).branch_token
402
# this leaves the repository with 1 lock
403
branch.repository.unlock()
404
except errors.LockContention:
405
return FailedSmartServerResponse(('LockContention',))
406
except errors.TokenMismatch:
407
return FailedSmartServerResponse(('TokenMismatch',))
408
except errors.UnlockableTransport:
409
return FailedSmartServerResponse(('UnlockableTransport',))
410
except errors.LockFailed, e:
411
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
412
if repo_token is None:
415
branch.repository.leave_lock_in_place()
416
branch.leave_lock_in_place()
418
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
421
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
423
def do_with_branch(self, branch, branch_token, repo_token):
425
branch.repository.lock_write(token=repo_token)
427
branch.lock_write(token=branch_token)
429
branch.repository.unlock()
430
except errors.TokenMismatch:
431
return FailedSmartServerResponse(('TokenMismatch',))
433
branch.repository.dont_leave_lock_in_place()
434
branch.dont_leave_lock_in_place()
436
return SuccessfulSmartServerResponse(('ok',))
439
class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest):
440
"""Get the physical lock status for a branch.
445
def do_with_branch(self, branch):
446
if branch.get_physical_lock_status():
447
return SuccessfulSmartServerResponse(('yes',))
449
return SuccessfulSmartServerResponse(('no',))