1
# Copyright (C) 2006 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."""
20
from bzrlib import errors
21
from bzrlib.bzrdir import BzrDir
22
from bzrlib.smart.request import (
23
FailedSmartServerResponse,
25
SuccessfulSmartServerResponse,
29
class SmartServerBranchRequest(SmartServerRequest):
30
"""Base class for handling common branch request logic.
33
def do(self, path, *args):
34
"""Execute a request for a branch at path.
36
All Branch requests take a path to the branch as their first argument.
38
If the branch is a branch reference, NotBranchError is raised.
40
:param path: The path for the repository as received from the
42
:return: A SmartServerResponse from self.do_with_branch().
44
transport = self.transport_from_client_path(path)
45
bzrdir = BzrDir.open_from_transport(transport)
46
if bzrdir.get_branch_reference() is not None:
47
raise errors.NotBranchError(transport.base)
48
branch = bzrdir.open_branch(ignore_fallbacks=True)
49
return self.do_with_branch(branch, *args)
52
class SmartServerLockedBranchRequest(SmartServerBranchRequest):
53
"""Base class for handling common branch request logic for requests that
57
def do_with_branch(self, branch, branch_token, repo_token, *args):
58
"""Execute a request for a branch.
60
A write lock will be acquired with the given tokens for the branch and
61
repository locks. The lock will be released once the request is
62
processed. The physical lock state won't be changed.
64
# XXX: write a test for LockContention
65
branch.repository.lock_write(token=repo_token)
67
branch.lock_write(token=branch_token)
69
return self.do_with_locked_branch(branch, *args)
73
branch.repository.unlock()
76
class SmartServerBranchGetConfigFile(SmartServerBranchRequest):
78
def do_with_branch(self, branch):
79
"""Return the content of branch.conf
81
The body is not utf8 decoded - its the literal bytestream from disk.
84
content = branch._transport.get_bytes('branch.conf')
85
except errors.NoSuchFile:
87
return SuccessfulSmartServerResponse( ('ok', ), content)
90
class SmartServerBranchGetParent(SmartServerBranchRequest):
92
def do_with_branch(self, branch):
93
"""Return the parent of branch."""
94
parent = branch._get_parent_location() or ''
95
return SuccessfulSmartServerResponse((parent,))
98
class SmartServerBranchGetTagsBytes(SmartServerBranchRequest):
100
def do_with_branch(self, branch):
101
"""Return the _get_tags_bytes for a branch."""
102
bytes = branch._get_tags_bytes()
103
return SuccessfulSmartServerResponse((bytes,))
106
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
108
def do_with_branch(self, branch):
109
stacked_on_url = branch.get_stacked_on_url()
110
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
113
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
115
def do_with_branch(self, branch):
116
"""Get the revision history for the branch.
118
The revision list is returned as the body content,
119
with each revision utf8 encoded and \x00 joined.
121
return SuccessfulSmartServerResponse(
122
('ok', ), ('\x00'.join(branch.revision_history())))
125
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
127
def do_with_branch(self, branch):
128
"""Return branch.last_revision_info().
130
The revno is encoded in decimal, the revision_id is encoded as utf8.
132
revno, last_revision = branch.last_revision_info()
133
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
136
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
137
"""Base class for handling common branch request logic for requests that
138
update the branch tip.
141
def do_with_locked_branch(self, branch, *args):
143
return self.do_tip_change_with_locked_branch(branch, *args)
144
except errors.TipChangeRejected, e:
146
if isinstance(msg, unicode):
147
msg = msg.encode('utf-8')
148
return FailedSmartServerResponse(('TipChangeRejected', msg))
151
class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest):
152
"""Set an option in the branch configuration."""
154
def do_with_locked_branch(self, branch, value, name, section):
157
branch._get_config().set_option(value.decode('utf8'), name, section)
158
return SuccessfulSmartServerResponse(())
161
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
163
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
164
if new_last_revision_id == 'null:':
165
branch.set_revision_history([])
167
if not branch.repository.has_revision(new_last_revision_id):
168
return FailedSmartServerResponse(
169
('NoSuchRevision', new_last_revision_id))
170
branch.set_revision_history(branch._lefthand_history(
171
new_last_revision_id, None, None))
172
return SuccessfulSmartServerResponse(('ok',))
175
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
177
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
178
allow_divergence, allow_overwrite_descendant):
179
"""Set the last revision of the branch.
183
:param new_last_revision_id: the revision ID to set as the last
184
revision of the branch.
185
:param allow_divergence: A flag. If non-zero, change the revision ID
186
even if the new_last_revision_id's ancestry has diverged from the
187
current last revision. If zero, a 'Diverged' error will be
188
returned if new_last_revision_id is not a descendant of the current
190
:param allow_overwrite_descendant: A flag. If zero and
191
new_last_revision_id is not a descendant of the current last
192
revision, then the last revision will not be changed. If non-zero
193
and there is no divergence, then the last revision is always
196
:returns: on success, a tuple of ('ok', revno, revision_id), where
197
revno and revision_id are the new values of the current last
198
revision info. The revision_id might be different to the
199
new_last_revision_id if allow_overwrite_descendant was not set.
201
do_not_overwrite_descendant = not allow_overwrite_descendant
203
last_revno, last_rev = branch.last_revision_info()
204
graph = branch.repository.get_graph()
205
if not allow_divergence or do_not_overwrite_descendant:
206
relation = branch._revision_relations(
207
last_rev, new_last_revision_id, graph)
208
if relation == 'diverged' and not allow_divergence:
209
return FailedSmartServerResponse(('Diverged',))
210
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
211
return SuccessfulSmartServerResponse(
212
('ok', last_revno, last_rev))
213
new_revno = graph.find_distance_to_null(
214
new_last_revision_id, [(last_rev, last_revno)])
215
branch.set_last_revision_info(new_revno, new_last_revision_id)
216
except errors.GhostRevisionsHaveNoRevno:
217
return FailedSmartServerResponse(
218
('NoSuchRevision', new_last_revision_id))
219
return SuccessfulSmartServerResponse(
220
('ok', new_revno, new_last_revision_id))
223
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
224
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
225
the specified branch.
230
def do_tip_change_with_locked_branch(self, branch, new_revno,
231
new_last_revision_id):
233
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
234
except errors.NoSuchRevision:
235
return FailedSmartServerResponse(
236
('NoSuchRevision', new_last_revision_id))
237
return SuccessfulSmartServerResponse(('ok',))
240
class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest):
241
"""Set the parent location for a branch.
243
Takes a location to set, which must be utf8 encoded.
246
def do_with_locked_branch(self, branch, location):
247
branch._set_parent_location(location)
248
return SuccessfulSmartServerResponse(())
251
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
253
def do_with_branch(self, branch, branch_token='', repo_token=''):
254
if branch_token == '':
259
repo_token = branch.repository.lock_write(token=repo_token)
261
branch_token = branch.lock_write(token=branch_token)
263
# this leaves the repository with 1 lock
264
branch.repository.unlock()
265
except errors.LockContention:
266
return FailedSmartServerResponse(('LockContention',))
267
except errors.TokenMismatch:
268
return FailedSmartServerResponse(('TokenMismatch',))
269
except errors.UnlockableTransport:
270
return FailedSmartServerResponse(('UnlockableTransport',))
271
except errors.LockFailed, e:
272
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
273
if repo_token is None:
276
branch.repository.leave_lock_in_place()
277
branch.leave_lock_in_place()
279
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
282
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
284
def do_with_branch(self, branch, branch_token, repo_token):
286
branch.repository.lock_write(token=repo_token)
288
branch.lock_write(token=branch_token)
290
branch.repository.unlock()
291
except errors.TokenMismatch:
292
return FailedSmartServerResponse(('TokenMismatch',))
294
branch.repository.dont_leave_lock_in_place()
295
branch.dont_leave_lock_in_place()
297
return SuccessfulSmartServerResponse(('ok',))