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_last_revision_info(0, new_last_revision_id)
304
if not branch.repository.has_revision(new_last_revision_id):
305
return FailedSmartServerResponse(
306
('NoSuchRevision', new_last_revision_id))
307
branch.generate_revision_history(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',))