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
from __future__ import absolute_import
19
"""Server-side branch related request implmentations."""
25
revision as _mod_revision,
27
from bzrlib.controldir import ControlDir
28
from bzrlib.smart.request import (
29
FailedSmartServerResponse,
31
SuccessfulSmartServerResponse,
35
class SmartServerBranchRequest(SmartServerRequest):
36
"""Base class for handling common branch request logic.
39
def do(self, path, *args):
40
"""Execute a request for a branch at path.
42
All Branch requests take a path to the branch as their first argument.
44
If the branch is a branch reference, NotBranchError is raised.
46
:param path: The path for the repository as received from the
48
:return: A SmartServerResponse from self.do_with_branch().
50
transport = self.transport_from_client_path(path)
51
controldir = ControlDir.open_from_transport(transport)
52
if controldir.get_branch_reference() is not None:
53
raise errors.NotBranchError(transport.base)
54
branch = controldir.open_branch(ignore_fallbacks=True)
55
return self.do_with_branch(branch, *args)
58
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
59
"""Base class for handling common branch request logic for requests that
63
def do_with_branch(self, branch, branch_token, repo_token, *args):
64
"""Execute a request for a branch.
66
A write lock will be acquired with the given tokens for the branch and
67
repository locks. The lock will be released once the request is
68
processed. The physical lock state won't be changed.
70
# XXX: write a test for LockContention
71
branch.repository.lock_write(token=repo_token)
73
branch.lock_write(token=branch_token)
75
return self.do_with_locked_branch(branch, *args)
79
branch.repository.unlock()
82
class SmartServerBranchBreakLock(SmartServerBranchRequest):
84
def do_with_branch(self, branch):
85
"""Break a branch lock.
88
return SuccessfulSmartServerResponse(('ok', ), )
91
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
93
def do_with_branch(self, branch):
94
"""Return the content of branch.conf
96
The body is not utf8 decoded - its the literal bytestream from disk.
99
content = branch.control_transport.get_bytes('branch.conf')
100
except errors.NoSuchFile:
102
return SuccessfulSmartServerResponse( ('ok', ), content)
105
class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
106
"""Set the configuration data for a branch.
111
def do_with_branch(self, branch, branch_token, repo_token):
112
"""Set the content of branch.conf.
114
The body is not utf8 decoded - its the literal bytestream for disk.
116
self._branch = branch
117
self._branch_token = branch_token
118
self._repo_token = repo_token
119
# Signal we want a body
122
def do_body(self, body_bytes):
123
self._branch.repository.lock_write(token=self._repo_token)
125
self._branch.lock_write(token=self._branch_token)
127
self._branch.control_transport.put_bytes(
128
'branch.conf', body_bytes)
130
self._branch.unlock()
132
self._branch.repository.unlock()
133
return SuccessfulSmartServerResponse(('ok', ))
136
class SmartServerBranchGetParent(SmartServerBranchRequest):
138
def do_with_branch(self, branch):
139
"""Return the parent of branch."""
140
parent = branch._get_parent_location() or ''
141
return SuccessfulSmartServerResponse((parent,))
144
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
146
def do_with_branch(self, branch):
147
"""Return the _get_tags_bytes for a branch."""
148
bytes = branch._get_tags_bytes()
149
return SuccessfulSmartServerResponse((bytes,))
152
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
154
def __init__(self, backing_transport, root_client_path='/', jail_root=None):
155
SmartServerLockedBranchRequest.__init__(
156
self, backing_transport, root_client_path, jail_root)
159
def do_with_locked_branch(self, branch):
160
"""Call _set_tags_bytes for a branch.
164
# We need to keep this branch locked until we get a body with the tags
167
self.branch.lock_write()
170
def do_body(self, bytes):
171
self.branch._set_tags_bytes(bytes)
172
return SuccessfulSmartServerResponse(())
175
# TODO: this request shouldn't have to do this housekeeping manually.
176
# Some of this logic probably belongs in a base class.
178
# We never acquired the branch successfully in the first place, so
179
# there's nothing more to do.
182
return SmartServerLockedBranchRequest.do_end(self)
184
# Only try unlocking if we locked successfully in the first place
188
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
190
def do_with_branch(self, branch):
191
"""Return the heads-to-fetch for a Branch as two bencoded lists.
193
See Branch.heads_to_fetch.
197
must_fetch, if_present_fetch = branch.heads_to_fetch()
198
return SuccessfulSmartServerResponse(
199
(list(must_fetch), list(if_present_fetch)))
202
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
204
def do_with_branch(self, branch):
205
stacked_on_url = branch.get_stacked_on_url()
206
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
209
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
211
def do_with_branch(self, branch):
212
"""Get the revision history for the branch.
214
The revision list is returned as the body content,
215
with each revision utf8 encoded and \x00 joined.
219
graph = branch.repository.get_graph()
220
stop_revisions = (None, _mod_revision.NULL_REVISION)
221
history = list(graph.iter_lefthand_ancestry(
222
branch.last_revision(), stop_revisions))
225
return SuccessfulSmartServerResponse(
226
('ok', ), ('\x00'.join(reversed(history))))
229
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
231
def do_with_branch(self, branch):
232
"""Return branch.last_revision_info().
234
The revno is encoded in decimal, the revision_id is encoded as utf8.
236
revno, last_revision = branch.last_revision_info()
237
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
240
class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest):
242
def do_with_branch(self, branch, revid):
243
"""Return branch.revision_id_to_revno().
247
The revno is encoded in decimal, the revision_id is encoded as utf8.
250
dotted_revno = branch.revision_id_to_dotted_revno(revid)
251
except errors.NoSuchRevision:
252
return FailedSmartServerResponse(('NoSuchRevision', revid))
253
return SuccessfulSmartServerResponse(
254
('ok', ) + tuple(map(str, dotted_revno)))
257
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
258
"""Base class for handling common branch request logic for requests that
259
update the branch tip.
262
def do_with_locked_branch(self, branch, *args):
264
return self.do_tip_change_with_locked_branch(branch, *args)
265
except errors.TipChangeRejected, e:
267
if isinstance(msg, unicode):
268
msg = msg.encode('utf-8')
269
return FailedSmartServerResponse(('TipChangeRejected', msg))
272
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
273
"""Set an option in the branch configuration."""
275
def do_with_locked_branch(self, branch, value, name, section):
278
branch._get_config().set_option(value.decode('utf8'), name, section)
279
return SuccessfulSmartServerResponse(())
282
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
283
"""Set an option in the branch configuration.
288
def do_with_locked_branch(self, branch, value_dict, name, section):
289
utf8_dict = bencode.bdecode(value_dict)
291
for key, value in utf8_dict.items():
292
value_dict[key.decode('utf8')] = value.decode('utf8')
295
branch._get_config().set_option(value_dict, name, section)
296
return SuccessfulSmartServerResponse(())
299
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
301
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
302
if new_last_revision_id == 'null:':
303
branch._set_revision_history([])
305
if not branch.repository.has_revision(new_last_revision_id):
306
return FailedSmartServerResponse(
307
('NoSuchRevision', new_last_revision_id))
308
branch._set_revision_history(branch._lefthand_history(
309
new_last_revision_id, None, None))
310
return SuccessfulSmartServerResponse(('ok',))
313
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
315
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
316
allow_divergence, allow_overwrite_descendant):
317
"""Set the last revision of the branch.
321
:param new_last_revision_id: the revision ID to set as the last
322
revision of the branch.
323
:param allow_divergence: A flag. If non-zero, change the revision ID
324
even if the new_last_revision_id's ancestry has diverged from the
325
current last revision. If zero, a 'Diverged' error will be
326
returned if new_last_revision_id is not a descendant of the current
328
:param allow_overwrite_descendant: A flag. If zero and
329
new_last_revision_id is not a descendant of the current last
330
revision, then the last revision will not be changed. If non-zero
331
and there is no divergence, then the last revision is always
334
:returns: on success, a tuple of ('ok', revno, revision_id), where
335
revno and revision_id are the new values of the current last
336
revision info. The revision_id might be different to the
337
new_last_revision_id if allow_overwrite_descendant was not set.
339
do_not_overwrite_descendant = not allow_overwrite_descendant
341
last_revno, last_rev = branch.last_revision_info()
342
graph = branch.repository.get_graph()
343
if not allow_divergence or do_not_overwrite_descendant:
344
relation = branch._revision_relations(
345
last_rev, new_last_revision_id, graph)
346
if relation == 'diverged' and not allow_divergence:
347
return FailedSmartServerResponse(('Diverged',))
348
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
349
return SuccessfulSmartServerResponse(
350
('ok', last_revno, last_rev))
351
new_revno = graph.find_distance_to_null(
352
new_last_revision_id, [(last_rev, last_revno)])
353
branch.set_last_revision_info(new_revno, new_last_revision_id)
354
except errors.GhostRevisionsHaveNoRevno:
355
return FailedSmartServerResponse(
356
('NoSuchRevision', new_last_revision_id))
357
return SuccessfulSmartServerResponse(
358
('ok', new_revno, new_last_revision_id))
361
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
362
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
363
the specified branch.
368
def do_tip_change_with_locked_branch(self, branch, new_revno,
369
new_last_revision_id):
371
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
372
except errors.NoSuchRevision:
373
return FailedSmartServerResponse(
374
('NoSuchRevision', new_last_revision_id))
375
return SuccessfulSmartServerResponse(('ok',))
378
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
379
"""Set the parent location for a branch.
381
Takes a location to set, which must be utf8 encoded.
384
def do_with_locked_branch(self, branch, location):
385
branch._set_parent_location(location)
386
return SuccessfulSmartServerResponse(())
389
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
391
def do_with_branch(self, branch, branch_token='', repo_token=''):
392
if branch_token == '':
397
repo_token = branch.repository.lock_write(
398
token=repo_token).repository_token
400
branch_token = branch.lock_write(
401
token=branch_token).branch_token
403
# this leaves the repository with 1 lock
404
branch.repository.unlock()
405
except errors.LockContention:
406
return FailedSmartServerResponse(('LockContention',))
407
except errors.TokenMismatch:
408
return FailedSmartServerResponse(('TokenMismatch',))
409
except errors.UnlockableTransport:
410
return FailedSmartServerResponse(('UnlockableTransport',))
411
except errors.LockFailed, e:
412
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
413
if repo_token is None:
416
branch.repository.leave_lock_in_place()
417
branch.leave_lock_in_place()
419
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
422
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
424
def do_with_branch(self, branch, branch_token, repo_token):
426
branch.repository.lock_write(token=repo_token)
428
branch.lock_write(token=branch_token)
430
branch.repository.unlock()
431
except errors.TokenMismatch:
432
return FailedSmartServerResponse(('TokenMismatch',))
434
branch.repository.dont_leave_lock_in_place()
435
branch.dont_leave_lock_in_place()
437
return SuccessfulSmartServerResponse(('ok',))
440
class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest):
441
"""Get the physical lock status for a branch.
446
def do_with_branch(self, branch):
447
if branch.get_physical_lock_status():
448
return SuccessfulSmartServerResponse(('yes',))
450
return SuccessfulSmartServerResponse(('no',))