1# Copyright (C) 2006-2010 Canonical Ltd 2# 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. 7# 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. 12# 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 16 17"""Server-side branch related request implmentations.""" 18 19from ... import ( 20 bencode, 21 errors, 22 revision as _mod_revision, 23 ) 24from ...controldir import ControlDir 25from .request import ( 26 FailedSmartServerResponse, 27 SmartServerRequest, 28 SuccessfulSmartServerResponse, 29 ) 30 31 32class SmartServerBranchRequest(SmartServerRequest): 33 """Base class for handling common branch request logic. 34 """ 35 36 def do(self, path, *args): 37 """Execute a request for a branch at path. 38 39 All Branch requests take a path to the branch as their first argument. 40 41 If the branch is a branch reference, NotBranchError is raised. 42 43 :param path: The path for the repository as received from the 44 client. 45 :return: A SmartServerResponse from self.do_with_branch(). 46 """ 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) 53 54 55class SmartServerLockedBranchRequest(SmartServerBranchRequest): 56 """Base class for handling common branch request logic for requests that 57 need a write lock. 58 """ 59 60 def do_with_branch(self, branch, branch_token, repo_token, *args): 61 """Execute a request for a branch. 62 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. 66 """ 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) 71 72 73class SmartServerBranchBreakLock(SmartServerBranchRequest): 74 75 def do_with_branch(self, branch): 76 """Break a branch lock. 77 """ 78 branch.break_lock() 79 return SuccessfulSmartServerResponse((b'ok', ), ) 80 81 82class SmartServerBranchGetConfigFile(SmartServerBranchRequest): 83 84 def do_with_branch(self, branch): 85 """Return the content of branch.conf 86 87 The body is not utf8 decoded - its the literal bytestream from disk. 88 """ 89 try: 90 content = branch.control_transport.get_bytes('branch.conf') 91 except errors.NoSuchFile: 92 content = b'' 93 return SuccessfulSmartServerResponse((b'ok', ), content) 94 95 96class SmartServerBranchPutConfigFile(SmartServerBranchRequest): 97 """Set the configuration data for a branch. 98 99 New in 2.5. 100 """ 101 102 def do_with_branch(self, branch, branch_token, repo_token): 103 """Set the content of branch.conf. 104 105 The body is not utf8 decoded - its the literal bytestream for disk. 106 """ 107 self._branch = branch 108 self._branch_token = branch_token 109 self._repo_token = repo_token 110 # Signal we want a body 111 return None 112 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', )) 119 120 121class SmartServerBranchGetParent(SmartServerBranchRequest): 122 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'),)) 127 128 129class SmartServerBranchGetTagsBytes(SmartServerBranchRequest): 130 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,)) 135 136 137class SmartServerBranchSetTagsBytes(SmartServerLockedBranchRequest): 138 139 def __init__(self, backing_transport, root_client_path='/', jail_root=None): 140 SmartServerLockedBranchRequest.__init__( 141 self, backing_transport, root_client_path, jail_root) 142 self.locked = False 143 144 def do_with_locked_branch(self, branch): 145 """Call _set_tags_bytes for a branch. 146 147 New in 1.18. 148 """ 149 # We need to keep this branch locked until we get a body with the tags 150 # bytes. 151 self.branch = branch 152 self.branch.lock_write() 153 self.locked = True 154 155 def do_body(self, bytes): 156 self.branch._set_tags_bytes(bytes) 157 return SuccessfulSmartServerResponse(()) 158 159 def do_end(self): 160 # TODO: this request shouldn't have to do this housekeeping manually. 161 # Some of this logic probably belongs in a base class. 162 if not self.locked: 163 # We never acquired the branch successfully in the first place, so 164 # there's nothing more to do. 165 return 166 try: 167 return SmartServerLockedBranchRequest.do_end(self) 168 finally: 169 # Only try unlocking if we locked successfully in the first place 170 self.branch.unlock() 171 172 173class SmartServerBranchHeadsToFetch(SmartServerBranchRequest): 174 175 def do_with_branch(self, branch): 176 """Return the heads-to-fetch for a Branch as two bencoded lists. 177 178 See Branch.heads_to_fetch. 179 180 New in 2.4. 181 """ 182 must_fetch, if_present_fetch = branch.heads_to_fetch() 183 return SuccessfulSmartServerResponse( 184 (list(must_fetch), list(if_present_fetch))) 185 186 187class SmartServerBranchRequestGetStackedOnURL(SmartServerBranchRequest): 188 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'))) 192 193 194class SmartServerRequestRevisionHistory(SmartServerBranchRequest): 195 196 def do_with_branch(self, branch): 197 """Get the revision history for the branch. 198 199 The revision list is returned as the body content, 200 with each revision utf8 encoded and \x00 joined. 201 """ 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)))) 209 210 211class SmartServerBranchRequestLastRevisionInfo(SmartServerBranchRequest): 212 213 def do_with_branch(self, branch): 214 """Return branch.last_revision_info(). 215 216 The revno is encoded in decimal, the revision_id is encoded as utf8. 217 """ 218 revno, last_revision = branch.last_revision_info() 219 return SuccessfulSmartServerResponse( 220 (b'ok', str(revno).encode('ascii'), last_revision)) 221 222 223class SmartServerBranchRequestRevisionIdToRevno(SmartServerBranchRequest): 224 225 def do_with_branch(self, branch, revid): 226 """Return branch.revision_id_to_revno(). 227 228 New in 2.5. 229 230 The revno is encoded in decimal, the revision_id is encoded as utf8. 231 """ 232 try: 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])) 242 243 244class SmartServerSetTipRequest(SmartServerLockedBranchRequest): 245 """Base class for handling common branch request logic for requests that 246 update the branch tip. 247 """ 248 249 def do_with_locked_branch(self, branch, *args): 250 try: 251 return self.do_tip_change_with_locked_branch(branch, *args) 252 except errors.TipChangeRejected as e: 253 msg = e.msg 254 if isinstance(msg, str): 255 msg = msg.encode('utf-8') 256 return FailedSmartServerResponse((b'TipChangeRejected', msg)) 257 258 259class SmartServerBranchRequestSetConfigOption(SmartServerLockedBranchRequest): 260 """Set an option in the branch configuration.""" 261 262 def do_with_locked_branch(self, branch, value, name, section): 263 if not section: 264 section = None 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(()) 269 270 271class SmartServerBranchRequestSetConfigOptionDict(SmartServerLockedBranchRequest): 272 """Set an option in the branch configuration. 273 274 New in 2.2. 275 """ 276 277 def do_with_locked_branch(self, branch, value_dict, name, section): 278 utf8_dict = bencode.bdecode(value_dict) 279 value_dict = {} 280 for key, value in utf8_dict.items(): 281 value_dict[key.decode('utf8')] = value.decode('utf8') 282 if not section: 283 section = None 284 else: 285 section = section.decode('utf-8') 286 branch._get_config().set_option(value_dict, name.decode('utf-8'), section) 287 return SuccessfulSmartServerResponse(()) 288 289 290class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest): 291 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) 295 else: 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',)) 301 302 303class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest): 304 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. 308 309 New in 1.6. 310 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 317 last revision. 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 322 changed. 323 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. 328 """ 329 do_not_overwrite_descendant = not allow_overwrite_descendant 330 try: 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)) 349 350 351class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest): 352 """Branch.set_last_revision_info. Sets the revno and the revision ID of 353 the specified branch. 354 355 New in breezy 1.4. 356 """ 357 358 def do_tip_change_with_locked_branch(self, branch, new_revno, 359 new_last_revision_id): 360 try: 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',)) 366 367 368class SmartServerBranchRequestSetParentLocation(SmartServerLockedBranchRequest): 369 """Set the parent location for a branch. 370 371 Takes a location to set, which must be utf8 encoded. 372 """ 373 374 def do_with_locked_branch(self, branch, location): 375 branch._set_parent_location(location.decode('utf-8')) 376 return SuccessfulSmartServerResponse(()) 377 378 379class SmartServerBranchRequestLockWrite(SmartServerBranchRequest): 380 381 def do_with_branch(self, branch, branch_token=b'', repo_token=b''): 382 if branch_token == b'': 383 branch_token = None 384 if repo_token == b'': 385 repo_token = None 386 try: 387 repo_token = branch.repository.lock_write( 388 token=repo_token).repository_token 389 try: 390 branch_token = branch.lock_write( 391 token=branch_token).token 392 finally: 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: 405 repo_token = b'' 406 else: 407 branch.repository.leave_lock_in_place() 408 branch.leave_lock_in_place() 409 branch.unlock() 410 return SuccessfulSmartServerResponse((b'ok', branch_token, repo_token)) 411 412 413class SmartServerBranchRequestUnlock(SmartServerBranchRequest): 414 415 def do_with_branch(self, branch, branch_token, repo_token): 416 try: 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',)) 421 if repo_token: 422 branch.repository.dont_leave_lock_in_place() 423 branch.dont_leave_lock_in_place() 424 branch.unlock() 425 return SuccessfulSmartServerResponse((b'ok',)) 426 427 428class SmartServerBranchRequestGetPhysicalLockStatus(SmartServerBranchRequest): 429 """Get the physical lock status for a branch. 430 431 New in 2.5. 432 """ 433 434 def do_with_branch(self, branch): 435 if branch.get_physical_lock_status(): 436 return SuccessfulSmartServerResponse((b'yes',)) 437 else: 438 return SuccessfulSmartServerResponse((b'no',)) 439 440 441class SmartServerBranchRequestGetAllReferenceInfo(SmartServerBranchRequest): 442 """Get the reference information. 443 444 New in 3.1. 445 """ 446 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) 453