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."""
22
revision as _mod_revision,
24
from ...controldir import ControlDir
25
from .request import (
26
FailedSmartServerResponse,
28
SuccessfulSmartServerResponse,
32
class SmartServerBranchRequest(SmartServerRequest):
33
"""Base class for handling common branch request logic.
36
def do(self, path, *args):
37
"""Execute a request for a branch at path.
39
All Branch requests take a path to the branch as their first argument.
41
If the branch is a branch reference, NotBranchError is raised.
43
:param path: The path for the repository as received from the
45
:return: A SmartServerResponse from self.do_with_branch().
47
transport = self.transport_from_client_path(path)
48
controldir = ControlDir.open_from_transport(transport)
49
if controldir.get_branch_reference() is not None:
50
raise errors.NotBranchError(transport.base)
51
branch = controldir.open_branch(ignore_fallbacks=True)
52
return self.do_with_branch(branch, *args)
55
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
56
"""Base class for handling common branch request logic for requests that
60
def do_with_branch(self, branch, branch_token, repo_token, *args):
61
"""Execute a request for a branch.
63
A write lock will be acquired with the given tokens for the branch and
64
repository locks. The lock will be released once the request is
65
processed. The physical lock state won't be changed.
67
# XXX: write a test for LockContention
68
with branch.repository.lock_write(token=repo_token), \
69
branch.lock_write(token=branch_token):
70
return self.do_with_locked_branch(branch, *args)
73
class SmartServerBranchBreakLock(SmartServerBranchRequest):
75
def do_with_branch(self, branch):
76
"""Break a branch lock.
79
return SuccessfulSmartServerResponse((b'ok', ), )
82
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
84
def do_with_branch(self, branch):
85
"""Return the content of branch.conf
87
The body is not utf8 decoded - its the literal bytestream from disk.
90
content = branch.control_transport.get_bytes('branch.conf')
91
except errors.NoSuchFile:
93
return SuccessfulSmartServerResponse((b'ok', ), content)
96
class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
97
"""Set the configuration data for a branch.
102
def do_with_branch(self, branch, branch_token, repo_token):
103
"""Set the content of branch.conf.
105
The body is not utf8 decoded - its the literal bytestream for disk.
107
self._branch = branch
108
self._branch_token = branch_token
109
self._repo_token = repo_token
110
# Signal we want a body
113
def do_body(self, body_bytes):
114
with self._branch.repository.lock_write(token=self._repo_token), \
115
self._branch.lock_write(token=self._branch_token):
116
self._branch.control_transport.put_bytes(
117
'branch.conf', body_bytes)
118
return SuccessfulSmartServerResponse((b'ok', ))
121
class SmartServerBranchGetParent(SmartServerBranchRequest):
123
def do_with_branch(self, branch):
124
"""Return the parent of branch."""
125
parent = branch._get_parent_location() or ''
126
return SuccessfulSmartServerResponse((parent.encode('utf-8'),))
129
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
131
def do_with_branch(self, branch):
132
"""Return the _get_tags_bytes for a branch."""
133
bytes = branch._get_tags_bytes()
134
return SuccessfulSmartServerResponse((bytes,))
137
class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest):
139
def __init__(self, backing_transport, root_client_path='/', jail_root=None):
140
SmartServerLockedBranchRequest.__init__(
141
self, backing_transport, root_client_path, jail_root)
144
def do_with_locked_branch(self, branch):
145
"""Call _set_tags_bytes for a branch.
149
# We need to keep this branch locked until we get a body with the tags
152
self.branch.lock_write()
155
def do_body(self, bytes):
156
self.branch._set_tags_bytes(bytes)
157
return SuccessfulSmartServerResponse(())
160
# TODO: this request shouldn't have to do this housekeeping manually.
161
# Some of this logic probably belongs in a base class.
163
# We never acquired the branch successfully in the first place, so
164
# there's nothing more to do.
167
return SmartServerLockedBranchRequest.do_end(self)
169
# Only try unlocking if we locked successfully in the first place
173
class SmartServerBranchHeadsToFetch(SmartServerBranchRequest):
175
def do_with_branch(self, branch):
176
"""Return the heads-to-fetch for a Branch as two bencoded lists.
178
See Branch.heads_to_fetch.
182
must_fetch, if_present_fetch = branch.heads_to_fetch()
183
return SuccessfulSmartServerResponse(
184
(list(must_fetch), list(if_present_fetch)))
187
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
189
def do_with_branch(self, branch):
190
stacked_on_url = branch.get_stacked_on_url()
191
return SuccessfulSmartServerResponse((b'ok', stacked_on_url.encode('ascii')))
194
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
196
def do_with_branch(self, branch):
197
"""Get the revision history for the branch.
199
The revision list is returned as the body content,
200
with each revision utf8 encoded and \x00 joined.
202
with branch.lock_read():
203
graph = branch.repository.get_graph()
204
stop_revisions = (None, _mod_revision.NULL_REVISION)
205
history = list(graph.iter_lefthand_ancestry(
206
branch.last_revision(), stop_revisions))
207
return SuccessfulSmartServerResponse(
208
(b'ok', ), (b'\x00'.join(reversed(history))))
211
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
213
def do_with_branch(self, branch):
214
"""Return branch.last_revision_info().
216
The revno is encoded in decimal, the revision_id is encoded as utf8.
218
revno, last_revision = branch.last_revision_info()
219
return SuccessfulSmartServerResponse(
220
(b'ok', str(revno).encode('ascii'), last_revision))
223
class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest):
225
def do_with_branch(self, branch, revid):
226
"""Return branch.revision_id_to_revno().
230
The revno is encoded in decimal, the revision_id is encoded as utf8.
233
dotted_revno = branch.revision_id_to_dotted_revno(revid)
234
except errors.NoSuchRevision:
235
return FailedSmartServerResponse((b'NoSuchRevision', revid))
236
except errors.GhostRevisionsHaveNoRevno as e:
237
return FailedSmartServerResponse(
238
(b'GhostRevisionsHaveNoRevno', e.revision_id,
239
e.ghost_revision_id))
240
return SuccessfulSmartServerResponse(
241
(b'ok', ) + tuple([b'%d' % x for x in dotted_revno]))
244
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
245
"""Base class for handling common branch request logic for requests that
246
update the branch tip.
249
def do_with_locked_branch(self, branch, *args):
251
return self.do_tip_change_with_locked_branch(branch, *args)
252
except errors.TipChangeRejected as e:
254
if isinstance(msg, str):
255
msg = msg.encode('utf-8')
256
return FailedSmartServerResponse((b'TipChangeRejected', msg))
259
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
260
"""Set an option in the branch configuration."""
262
def do_with_locked_branch(self, branch, value, name, section):
265
branch._get_config().set_option(
266
value.decode('utf-8'), name.decode('utf-8'),
267
section.decode('utf-8') if section is not None else None)
268
return SuccessfulSmartServerResponse(())
271
class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest):
272
"""Set an option in the branch configuration.
277
def do_with_locked_branch(self, branch, value_dict, name, section):
278
utf8_dict = bencode.bdecode(value_dict)
280
for key, value in utf8_dict.items():
281
value_dict[key.decode('utf8')] = value.decode('utf8')
285
section = section.decode('utf-8')
286
branch._get_config().set_option(value_dict, name.decode('utf-8'), section)
287
return SuccessfulSmartServerResponse(())
290
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
292
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
293
if new_last_revision_id == b'null:':
294
branch.set_last_revision_info(0, new_last_revision_id)
296
if not branch.repository.has_revision(new_last_revision_id):
297
return FailedSmartServerResponse(
298
(b'NoSuchRevision', new_last_revision_id))
299
branch.generate_revision_history(new_last_revision_id, None, None)
300
return SuccessfulSmartServerResponse((b'ok',))
303
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
305
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
306
allow_divergence, allow_overwrite_descendant):
307
"""Set the last revision of the branch.
311
:param new_last_revision_id: the revision ID to set as the last
312
revision of the branch.
313
:param allow_divergence: A flag. If non-zero, change the revision ID
314
even if the new_last_revision_id's ancestry has diverged from the
315
current last revision. If zero, a 'Diverged' error will be
316
returned if new_last_revision_id is not a descendant of the current
318
:param allow_overwrite_descendant: A flag. If zero and
319
new_last_revision_id is not a descendant of the current last
320
revision, then the last revision will not be changed. If non-zero
321
and there is no divergence, then the last revision is always
324
:returns: on success, a tuple of ('ok', revno, revision_id), where
325
revno and revision_id are the new values of the current last
326
revision info. The revision_id might be different to the
327
new_last_revision_id if allow_overwrite_descendant was not set.
329
do_not_overwrite_descendant = not allow_overwrite_descendant
331
last_revno, last_rev = branch.last_revision_info()
332
graph = branch.repository.get_graph()
333
if not allow_divergence or do_not_overwrite_descendant:
334
relation = branch._revision_relations(
335
last_rev, new_last_revision_id, graph)
336
if relation == 'diverged' and not allow_divergence:
337
return FailedSmartServerResponse((b'Diverged',))
338
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
339
return SuccessfulSmartServerResponse(
340
(b'ok', last_revno, last_rev))
341
new_revno = graph.find_distance_to_null(
342
new_last_revision_id, [(last_rev, last_revno)])
343
branch.set_last_revision_info(new_revno, new_last_revision_id)
344
except errors.GhostRevisionsHaveNoRevno:
345
return FailedSmartServerResponse(
346
(b'NoSuchRevision', new_last_revision_id))
347
return SuccessfulSmartServerResponse(
348
(b'ok', new_revno, new_last_revision_id))
351
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
352
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
353
the specified branch.
358
def do_tip_change_with_locked_branch(self, branch, new_revno,
359
new_last_revision_id):
361
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
362
except errors.NoSuchRevision:
363
return FailedSmartServerResponse(
364
(b'NoSuchRevision', new_last_revision_id))
365
return SuccessfulSmartServerResponse((b'ok',))
368
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
369
"""Set the parent location for a branch.
371
Takes a location to set, which must be utf8 encoded.
374
def do_with_locked_branch(self, branch, location):
375
branch._set_parent_location(location.decode('utf-8'))
376
return SuccessfulSmartServerResponse(())
379
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
381
def do_with_branch(self, branch, branch_token=b'', repo_token=b''):
382
if branch_token == b'':
384
if repo_token == b'':
387
repo_token = branch.repository.lock_write(
388
token=repo_token).repository_token
390
branch_token = branch.lock_write(
391
token=branch_token).token
393
# this leaves the repository with 1 lock
394
branch.repository.unlock()
395
except errors.LockContention:
396
return FailedSmartServerResponse((b'LockContention',))
397
except errors.TokenMismatch:
398
return FailedSmartServerResponse((b'TokenMismatch',))
399
except errors.UnlockableTransport:
400
return FailedSmartServerResponse((b'UnlockableTransport',))
401
except errors.LockFailed as e:
402
return FailedSmartServerResponse((b'LockFailed',
403
str(e.lock).encode('utf-8'), str(e.why).encode('utf-8')))
404
if repo_token is None:
407
branch.repository.leave_lock_in_place()
408
branch.leave_lock_in_place()
410
return SuccessfulSmartServerResponse((b'ok', branch_token, repo_token))
413
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
415
def do_with_branch(self, branch, branch_token, repo_token):
417
with branch.repository.lock_write(token=repo_token):
418
branch.lock_write(token=branch_token)
419
except errors.TokenMismatch:
420
return FailedSmartServerResponse((b'TokenMismatch',))
422
branch.repository.dont_leave_lock_in_place()
423
branch.dont_leave_lock_in_place()
425
return SuccessfulSmartServerResponse((b'ok',))
428
class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest):
429
"""Get the physical lock status for a branch.
434
def do_with_branch(self, branch):
435
if branch.get_physical_lock_status():
436
return SuccessfulSmartServerResponse((b'yes',))
438
return SuccessfulSmartServerResponse((b'no',))
441
class SmartServerBranchRequestGetAllReferenceInfo(SmartServerBranchRequest):
442
"""Get the reference information.
447
def do_with_branch(self, branch):
448
all_reference_info = branch._get_all_reference_info()
449
content = bencode.bencode([
450
(key, value[0].encode('utf-8'), value[1].encode('utf-8') if value[1] else b'')
451
for (key, value) in all_reference_info.items()])
452
return SuccessfulSmartServerResponse((b'ok', ), content)