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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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()
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.
83
# This was at one time called by RemoteBranchLockableFiles
84
# intercepting access to this file; as of 1.5 it is not called by the
85
# client but retained for compatibility. It may be called again to
86
# allow the client to get the configuration without needing vfs
89
content = branch._transport.get_bytes('branch.conf')
90
except errors.NoSuchFile:
92
return SuccessfulSmartServerResponse( ('ok', ), content)
95
class SmartServerBranchGetParent(SmartServerBranchRequest):
97
def do_with_branch(self, branch):
98
"""Return the parent of branch."""
99
parent = branch._get_parent_location() or ''
100
return SuccessfulSmartServerResponse((parent,))
103
class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest):
105
def do_with_branch(self, branch):
106
stacked_on_url = branch.get_stacked_on_url()
107
return SuccessfulSmartServerResponse(('ok', stacked_on_url))
110
class SmartServerRequestRevisionHistory(SmartServerBranchRequest):
112
def do_with_branch(self, branch):
113
"""Get the revision history for the branch.
115
The revision list is returned as the body content,
116
with each revision utf8 encoded and \x00 joined.
118
return SuccessfulSmartServerResponse(
119
('ok', ), ('\x00'.join(branch.revision_history())))
122
class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest):
124
def do_with_branch(self, branch):
125
"""Return branch.last_revision_info().
127
The revno is encoded in decimal, the revision_id is encoded as utf8.
129
revno, last_revision = branch.last_revision_info()
130
return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
133
class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
134
"""Base class for handling common branch request logic for requests that
135
update the branch tip.
138
def do_with_locked_branch(self, branch, *args):
140
return self.do_tip_change_with_locked_branch(branch, *args)
141
except errors.TipChangeRejected, e:
143
if isinstance(msg, unicode):
144
msg = msg.encode('utf-8')
145
return FailedSmartServerResponse(('TipChangeRejected', msg))
148
class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
150
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
151
if new_last_revision_id == 'null:':
152
branch.set_revision_history([])
154
if not branch.repository.has_revision(new_last_revision_id):
155
return FailedSmartServerResponse(
156
('NoSuchRevision', new_last_revision_id))
157
branch.set_revision_history(branch._lefthand_history(
158
new_last_revision_id, None, None))
159
return SuccessfulSmartServerResponse(('ok',))
162
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
164
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
165
allow_divergence, allow_overwrite_descendant):
166
"""Set the last revision of the branch.
170
:param new_last_revision_id: the revision ID to set as the last
171
revision of the branch.
172
:param allow_divergence: A flag. If non-zero, change the revision ID
173
even if the new_last_revision_id's ancestry has diverged from the
174
current last revision. If zero, a 'Diverged' error will be
175
returned if new_last_revision_id is not a descendant of the current
177
:param allow_overwrite_descendant: A flag. If zero and
178
new_last_revision_id is not a descendant of the current last
179
revision, then the last revision will not be changed. If non-zero
180
and there is no divergence, then the last revision is always
183
:returns: on success, a tuple of ('ok', revno, revision_id), where
184
revno and revision_id are the new values of the current last
185
revision info. The revision_id might be different to the
186
new_last_revision_id if allow_overwrite_descendant was not set.
188
do_not_overwrite_descendant = not allow_overwrite_descendant
190
last_revno, last_rev = branch.last_revision_info()
191
graph = branch.repository.get_graph()
192
if not allow_divergence or do_not_overwrite_descendant:
193
relation = branch._revision_relations(
194
last_rev, new_last_revision_id, graph)
195
if relation == 'diverged' and not allow_divergence:
196
return FailedSmartServerResponse(('Diverged',))
197
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
198
return SuccessfulSmartServerResponse(
199
('ok', last_revno, last_rev))
200
new_revno = graph.find_distance_to_null(
201
new_last_revision_id, [(last_rev, last_revno)])
202
branch.set_last_revision_info(new_revno, new_last_revision_id)
203
except errors.GhostRevisionsHaveNoRevno:
204
return FailedSmartServerResponse(
205
('NoSuchRevision', new_last_revision_id))
206
return SuccessfulSmartServerResponse(
207
('ok', new_revno, new_last_revision_id))
210
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
211
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
212
the specified branch.
217
def do_tip_change_with_locked_branch(self, branch, new_revno,
218
new_last_revision_id):
220
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
221
except errors.NoSuchRevision:
222
return FailedSmartServerResponse(
223
('NoSuchRevision', new_last_revision_id))
224
return SuccessfulSmartServerResponse(('ok',))
227
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
229
def do_with_branch(self, branch, branch_token='', repo_token=''):
230
if branch_token == '':
235
repo_token = branch.repository.lock_write(token=repo_token)
237
branch_token = branch.lock_write(token=branch_token)
239
# this leaves the repository with 1 lock
240
branch.repository.unlock()
241
except errors.LockContention:
242
return FailedSmartServerResponse(('LockContention',))
243
except errors.TokenMismatch:
244
return FailedSmartServerResponse(('TokenMismatch',))
245
except errors.UnlockableTransport:
246
return FailedSmartServerResponse(('UnlockableTransport',))
247
except errors.LockFailed, e:
248
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
249
if repo_token is None:
252
branch.repository.leave_lock_in_place()
253
branch.leave_lock_in_place()
255
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
258
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
260
def do_with_branch(self, branch, branch_token, repo_token):
262
branch.repository.lock_write(token=repo_token)
264
branch.lock_write(token=branch_token)
266
branch.repository.unlock()
267
except errors.TokenMismatch:
268
return FailedSmartServerResponse(('TokenMismatch',))
270
branch.repository.dont_leave_lock_in_place()
271
branch.dont_leave_lock_in_place()
273
return SuccessfulSmartServerResponse(('ok',))