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 SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
153
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
154
if new_last_revision_id == 'null:':
155
branch.set_revision_history([])
157
if not branch.repository.has_revision(new_last_revision_id):
158
return FailedSmartServerResponse(
159
('NoSuchRevision', new_last_revision_id))
160
branch.set_revision_history(branch._lefthand_history(
161
new_last_revision_id, None, None))
162
return SuccessfulSmartServerResponse(('ok',))
165
class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
167
def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
168
allow_divergence, allow_overwrite_descendant):
169
"""Set the last revision of the branch.
173
:param new_last_revision_id: the revision ID to set as the last
174
revision of the branch.
175
:param allow_divergence: A flag. If non-zero, change the revision ID
176
even if the new_last_revision_id's ancestry has diverged from the
177
current last revision. If zero, a 'Diverged' error will be
178
returned if new_last_revision_id is not a descendant of the current
180
:param allow_overwrite_descendant: A flag. If zero and
181
new_last_revision_id is not a descendant of the current last
182
revision, then the last revision will not be changed. If non-zero
183
and there is no divergence, then the last revision is always
186
:returns: on success, a tuple of ('ok', revno, revision_id), where
187
revno and revision_id are the new values of the current last
188
revision info. The revision_id might be different to the
189
new_last_revision_id if allow_overwrite_descendant was not set.
191
do_not_overwrite_descendant = not allow_overwrite_descendant
193
last_revno, last_rev = branch.last_revision_info()
194
graph = branch.repository.get_graph()
195
if not allow_divergence or do_not_overwrite_descendant:
196
relation = branch._revision_relations(
197
last_rev, new_last_revision_id, graph)
198
if relation == 'diverged' and not allow_divergence:
199
return FailedSmartServerResponse(('Diverged',))
200
if relation == 'a_descends_from_b' and do_not_overwrite_descendant:
201
return SuccessfulSmartServerResponse(
202
('ok', last_revno, last_rev))
203
new_revno = graph.find_distance_to_null(
204
new_last_revision_id, [(last_rev, last_revno)])
205
branch.set_last_revision_info(new_revno, new_last_revision_id)
206
except errors.GhostRevisionsHaveNoRevno:
207
return FailedSmartServerResponse(
208
('NoSuchRevision', new_last_revision_id))
209
return SuccessfulSmartServerResponse(
210
('ok', new_revno, new_last_revision_id))
213
class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
214
"""Branch.set_last_revision_info. Sets the revno and the revision ID of
215
the specified branch.
220
def do_tip_change_with_locked_branch(self, branch, new_revno,
221
new_last_revision_id):
223
branch.set_last_revision_info(int(new_revno), new_last_revision_id)
224
except errors.NoSuchRevision:
225
return FailedSmartServerResponse(
226
('NoSuchRevision', new_last_revision_id))
227
return SuccessfulSmartServerResponse(('ok',))
230
class SmartServerBranchRequestLockWrite(SmartServerBranchRequest):
232
def do_with_branch(self, branch, branch_token='', repo_token=''):
233
if branch_token == '':
238
repo_token = branch.repository.lock_write(token=repo_token)
240
branch_token = branch.lock_write(token=branch_token)
242
# this leaves the repository with 1 lock
243
branch.repository.unlock()
244
except errors.LockContention:
245
return FailedSmartServerResponse(('LockContention',))
246
except errors.TokenMismatch:
247
return FailedSmartServerResponse(('TokenMismatch',))
248
except errors.UnlockableTransport:
249
return FailedSmartServerResponse(('UnlockableTransport',))
250
except errors.LockFailed, e:
251
return FailedSmartServerResponse(('LockFailed', str(e.lock), str(e.why)))
252
if repo_token is None:
255
branch.repository.leave_lock_in_place()
256
branch.leave_lock_in_place()
258
return SuccessfulSmartServerResponse(('ok', branch_token, repo_token))
261
class SmartServerBranchRequestUnlock(SmartServerBranchRequest):
263
def do_with_branch(self, branch, branch_token, repo_token):
265
branch.repository.lock_write(token=repo_token)
267
branch.lock_write(token=branch_token)
269
branch.repository.unlock()
270
except errors.TokenMismatch:
271
return FailedSmartServerResponse(('TokenMismatch',))
273
branch.repository.dont_leave_lock_in_place()
274
branch.dont_leave_lock_in_place()
276
return SuccessfulSmartServerResponse(('ok',))