bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
6653.1.1
by Jelmer Vernooij
Split bzr branch code out into breezy.bzrbranch. |
1 |
# Copyright (C) 2005-2012 Canonical Ltd
|
2 |
# Copyright (C) 2017 Breezy Developers
|
|
3 |
#
|
|
4 |
# This program is free software; you can redistribute it and/or modify
|
|
5 |
# it under the terms of the GNU General Public License as published by
|
|
6 |
# the Free Software Foundation; either version 2 of the License, or
|
|
7 |
# (at your option) any later version.
|
|
8 |
#
|
|
9 |
# This program is distributed in the hope that it will be useful,
|
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
# GNU General Public License for more details.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License
|
|
15 |
# along with this program; if not, write to the Free Software
|
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17 |
||
18 |
from __future__ import absolute_import |
|
19 |
||
6653.1.3
by Jelmer Vernooij
Use lazy imports. |
20 |
from .lazy_import import lazy_import |
21 |
lazy_import(globals(), """ |
|
22 |
from breezy import (
|
|
6653.1.1
by Jelmer Vernooij
Split bzr branch code out into breezy.bzrbranch. |
23 |
cache_utf8,
|
24 |
config as _mod_config,
|
|
25 |
lockable_files,
|
|
26 |
lockdir,
|
|
27 |
rio,
|
|
28 |
shelf,
|
|
29 |
tag as _mod_tag,
|
|
6653.1.3
by Jelmer Vernooij
Use lazy imports. |
30 |
)
|
31 |
""") |
|
32 |
||
33 |
from . import ( |
|
34 |
bzrdir, |
|
35 |
controldir, |
|
36 |
errors, |
|
37 |
revision as _mod_revision, |
|
6653.1.1
by Jelmer Vernooij
Split bzr branch code out into breezy.bzrbranch. |
38 |
urlutils, |
39 |
)
|
|
40 |
from .branch import ( |
|
41 |
Branch, |
|
42 |
BranchFormat, |
|
43 |
BranchWriteLockResult, |
|
44 |
format_registry, |
|
45 |
)
|
|
46 |
from .decorators import ( |
|
47 |
needs_read_lock, |
|
48 |
needs_write_lock, |
|
49 |
only_raises, |
|
50 |
)
|
|
51 |
from .lock import _RelockDebugMixin, LogicalLockResult |
|
52 |
from .sixish import ( |
|
53 |
BytesIO, |
|
6653.1.8
by Jelmer Vernooij
Merge trunk. |
54 |
viewitems, |
6653.1.1
by Jelmer Vernooij
Split bzr branch code out into breezy.bzrbranch. |
55 |
)
|
56 |
from .trace import ( |
|
57 |
mutter, |
|
58 |
)
|
|
59 |
||
60 |
||
61 |
class BzrBranch(Branch, _RelockDebugMixin): |
|
62 |
"""A branch stored in the actual filesystem. |
|
63 |
||
64 |
Note that it's "local" in the context of the filesystem; it doesn't
|
|
65 |
really matter if it's on an nfs/smb/afs/coda/... share, as long as
|
|
66 |
it's writable, and can be accessed via the normal filesystem API.
|
|
67 |
||
68 |
:ivar _transport: Transport for file operations on this branch's
|
|
69 |
control files, typically pointing to the .bzr/branch directory.
|
|
70 |
:ivar repository: Repository for this branch.
|
|
71 |
:ivar base: The url of the base directory for this branch; the one
|
|
72 |
containing the .bzr directory.
|
|
73 |
:ivar name: Optional colocated branch name as it exists in the control
|
|
74 |
directory.
|
|
75 |
"""
|
|
76 |
||
77 |
def __init__(self, _format=None, |
|
78 |
_control_files=None, a_bzrdir=None, name=None, |
|
79 |
_repository=None, ignore_fallbacks=False, |
|
80 |
possible_transports=None): |
|
81 |
"""Create new branch object at a particular location.""" |
|
82 |
if a_bzrdir is None: |
|
83 |
raise ValueError('a_bzrdir must be supplied') |
|
84 |
if name is None: |
|
85 |
raise ValueError('name must be supplied') |
|
86 |
self.bzrdir = a_bzrdir |
|
87 |
self._user_transport = self.bzrdir.transport.clone('..') |
|
88 |
if name != "": |
|
89 |
self._user_transport.set_segment_parameter( |
|
90 |
"branch", urlutils.escape(name)) |
|
91 |
self._base = self._user_transport.base |
|
92 |
self.name = name |
|
93 |
self._format = _format |
|
94 |
if _control_files is None: |
|
95 |
raise ValueError('BzrBranch _control_files is None') |
|
96 |
self.control_files = _control_files |
|
97 |
self._transport = _control_files._transport |
|
98 |
self.repository = _repository |
|
99 |
self.conf_store = None |
|
100 |
Branch.__init__(self, possible_transports) |
|
101 |
||
102 |
def __str__(self): |
|
103 |
return '%s(%s)' % (self.__class__.__name__, self.user_url) |
|
104 |
||
105 |
__repr__ = __str__ |
|
106 |
||
107 |
def _get_base(self): |
|
108 |
"""Returns the directory containing the control directory.""" |
|
109 |
return self._base |
|
110 |
||
111 |
base = property(_get_base, doc="The URL for the root of this branch.") |
|
112 |
||
113 |
@property
|
|
114 |
def user_transport(self): |
|
115 |
return self._user_transport |
|
116 |
||
117 |
def _get_config(self): |
|
118 |
return _mod_config.TransportConfig(self._transport, 'branch.conf') |
|
119 |
||
120 |
def _get_config_store(self): |
|
121 |
if self.conf_store is None: |
|
122 |
self.conf_store = _mod_config.BranchStore(self) |
|
123 |
return self.conf_store |
|
124 |
||
125 |
def _uncommitted_branch(self): |
|
126 |
"""Return the branch that may contain uncommitted changes.""" |
|
127 |
master = self.get_master_branch() |
|
128 |
if master is not None: |
|
129 |
return master |
|
130 |
else: |
|
131 |
return self |
|
132 |
||
133 |
def store_uncommitted(self, creator): |
|
134 |
"""Store uncommitted changes from a ShelfCreator. |
|
135 |
||
136 |
:param creator: The ShelfCreator containing uncommitted changes, or
|
|
137 |
None to delete any stored changes.
|
|
138 |
:raises: ChangesAlreadyStored if the branch already has changes.
|
|
139 |
"""
|
|
140 |
branch = self._uncommitted_branch() |
|
141 |
if creator is None: |
|
142 |
branch._transport.delete('stored-transform') |
|
143 |
return
|
|
144 |
if branch._transport.has('stored-transform'): |
|
145 |
raise errors.ChangesAlreadyStored |
|
146 |
transform = BytesIO() |
|
147 |
creator.write_shelf(transform) |
|
148 |
transform.seek(0) |
|
149 |
branch._transport.put_file('stored-transform', transform) |
|
150 |
||
151 |
def get_unshelver(self, tree): |
|
152 |
"""Return a shelf.Unshelver for this branch and tree. |
|
153 |
||
154 |
:param tree: The tree to use to construct the Unshelver.
|
|
155 |
:return: an Unshelver or None if no changes are stored.
|
|
156 |
"""
|
|
157 |
branch = self._uncommitted_branch() |
|
158 |
try: |
|
159 |
transform = branch._transport.get('stored-transform') |
|
160 |
except errors.NoSuchFile: |
|
161 |
return None |
|
162 |
return shelf.Unshelver.from_tree_and_shelf(tree, transform) |
|
163 |
||
164 |
def is_locked(self): |
|
165 |
return self.control_files.is_locked() |
|
166 |
||
167 |
def lock_write(self, token=None): |
|
168 |
"""Lock the branch for write operations. |
|
169 |
||
170 |
:param token: A token to permit reacquiring a previously held and
|
|
171 |
preserved lock.
|
|
172 |
:return: A BranchWriteLockResult.
|
|
173 |
"""
|
|
174 |
if not self.is_locked(): |
|
175 |
self._note_lock('w') |
|
176 |
self.repository._warn_if_deprecated(self) |
|
177 |
self.repository.lock_write() |
|
178 |
took_lock = True |
|
179 |
else: |
|
180 |
took_lock = False |
|
181 |
try: |
|
182 |
return BranchWriteLockResult(self.unlock, |
|
183 |
self.control_files.lock_write(token=token)) |
|
184 |
except: |
|
185 |
if took_lock: |
|
186 |
self.repository.unlock() |
|
187 |
raise
|
|
188 |
||
189 |
def lock_read(self): |
|
190 |
"""Lock the branch for read operations. |
|
191 |
||
192 |
:return: A breezy.lock.LogicalLockResult.
|
|
193 |
"""
|
|
194 |
if not self.is_locked(): |
|
195 |
self._note_lock('r') |
|
196 |
self.repository._warn_if_deprecated(self) |
|
197 |
self.repository.lock_read() |
|
198 |
took_lock = True |
|
199 |
else: |
|
200 |
took_lock = False |
|
201 |
try: |
|
202 |
self.control_files.lock_read() |
|
203 |
return LogicalLockResult(self.unlock) |
|
204 |
except: |
|
205 |
if took_lock: |
|
206 |
self.repository.unlock() |
|
207 |
raise
|
|
208 |
||
209 |
@only_raises(errors.LockNotHeld, errors.LockBroken) |
|
210 |
def unlock(self): |
|
211 |
if self.control_files._lock_count == 1 and self.conf_store is not None: |
|
212 |
self.conf_store.save_changes() |
|
213 |
try: |
|
214 |
self.control_files.unlock() |
|
215 |
finally: |
|
216 |
if not self.control_files.is_locked(): |
|
217 |
self.repository.unlock() |
|
218 |
# we just released the lock
|
|
219 |
self._clear_cached_state() |
|
220 |
||
221 |
def peek_lock_mode(self): |
|
222 |
if self.control_files._lock_count == 0: |
|
223 |
return None |
|
224 |
else: |
|
225 |
return self.control_files._lock_mode |
|
226 |
||
227 |
def get_physical_lock_status(self): |
|
228 |
return self.control_files.get_physical_lock_status() |
|
229 |
||
230 |
@needs_read_lock
|
|
231 |
def print_file(self, file, revision_id): |
|
232 |
"""See Branch.print_file.""" |
|
233 |
return self.repository.print_file(file, revision_id) |
|
234 |
||
235 |
@needs_write_lock
|
|
236 |
def set_last_revision_info(self, revno, revision_id): |
|
237 |
if not revision_id or not isinstance(revision_id, basestring): |
|
238 |
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self) |
|
239 |
revision_id = _mod_revision.ensure_null(revision_id) |
|
240 |
old_revno, old_revid = self.last_revision_info() |
|
241 |
if self.get_append_revisions_only(): |
|
242 |
self._check_history_violation(revision_id) |
|
243 |
self._run_pre_change_branch_tip_hooks(revno, revision_id) |
|
244 |
self._write_last_revision_info(revno, revision_id) |
|
245 |
self._clear_cached_state() |
|
246 |
self._last_revision_info_cache = revno, revision_id |
|
247 |
self._run_post_change_branch_tip_hooks(old_revno, old_revid) |
|
248 |
||
249 |
def basis_tree(self): |
|
250 |
"""See Branch.basis_tree.""" |
|
251 |
return self.repository.revision_tree(self.last_revision()) |
|
252 |
||
253 |
def _get_parent_location(self): |
|
254 |
_locs = ['parent', 'pull', 'x-pull'] |
|
255 |
for l in _locs: |
|
256 |
try: |
|
257 |
return self._transport.get_bytes(l).strip('\n') |
|
258 |
except errors.NoSuchFile: |
|
259 |
pass
|
|
260 |
return None |
|
261 |
||
262 |
def get_stacked_on_url(self): |
|
263 |
raise errors.UnstackableBranchFormat(self._format, self.user_url) |
|
264 |
||
265 |
def set_push_location(self, location): |
|
266 |
"""See Branch.set_push_location.""" |
|
267 |
self.get_config().set_user_option( |
|
268 |
'push_location', location, |
|
269 |
store=_mod_config.STORE_LOCATION_NORECURSE) |
|
270 |
||
271 |
def _set_parent_location(self, url): |
|
272 |
if url is None: |
|
273 |
self._transport.delete('parent') |
|
274 |
else: |
|
275 |
self._transport.put_bytes('parent', url + '\n', |
|
276 |
mode=self.bzrdir._get_file_mode()) |
|
277 |
||
278 |
@needs_write_lock
|
|
279 |
def unbind(self): |
|
280 |
"""If bound, unbind""" |
|
281 |
return self.set_bound_location(None) |
|
282 |
||
283 |
@needs_write_lock
|
|
284 |
def bind(self, other): |
|
285 |
"""Bind this branch to the branch other. |
|
286 |
||
287 |
This does not push or pull data between the branches, though it does
|
|
288 |
check for divergence to raise an error when the branches are not
|
|
289 |
either the same, or one a prefix of the other. That behaviour may not
|
|
290 |
be useful, so that check may be removed in future.
|
|
291 |
||
292 |
:param other: The branch to bind to
|
|
293 |
:type other: Branch
|
|
294 |
"""
|
|
295 |
# TODO: jam 20051230 Consider checking if the target is bound
|
|
296 |
# It is debatable whether you should be able to bind to
|
|
297 |
# a branch which is itself bound.
|
|
298 |
# Committing is obviously forbidden,
|
|
299 |
# but binding itself may not be.
|
|
300 |
# Since we *have* to check at commit time, we don't
|
|
301 |
# *need* to check here
|
|
302 |
||
303 |
# we want to raise diverged if:
|
|
304 |
# last_rev is not in the other_last_rev history, AND
|
|
305 |
# other_last_rev is not in our history, and do it without pulling
|
|
306 |
# history around
|
|
307 |
self.set_bound_location(other.base) |
|
308 |
||
309 |
def get_bound_location(self): |
|
310 |
try: |
|
311 |
return self._transport.get_bytes('bound')[:-1] |
|
312 |
except errors.NoSuchFile: |
|
313 |
return None |
|
314 |
||
315 |
@needs_read_lock
|
|
316 |
def get_master_branch(self, possible_transports=None): |
|
317 |
"""Return the branch we are bound to. |
|
318 |
||
319 |
:return: Either a Branch, or None
|
|
320 |
"""
|
|
321 |
if self._master_branch_cache is None: |
|
322 |
self._master_branch_cache = self._get_master_branch( |
|
323 |
possible_transports) |
|
324 |
return self._master_branch_cache |
|
325 |
||
326 |
def _get_master_branch(self, possible_transports): |
|
327 |
bound_loc = self.get_bound_location() |
|
328 |
if not bound_loc: |
|
329 |
return None |
|
330 |
try: |
|
331 |
return Branch.open(bound_loc, |
|
332 |
possible_transports=possible_transports) |
|
333 |
except (errors.NotBranchError, errors.ConnectionError) as e: |
|
334 |
raise errors.BoundBranchConnectionFailure( |
|
335 |
self, bound_loc, e) |
|
336 |
||
337 |
@needs_write_lock
|
|
338 |
def set_bound_location(self, location): |
|
339 |
"""Set the target where this branch is bound to. |
|
340 |
||
341 |
:param location: URL to the target branch
|
|
342 |
"""
|
|
343 |
self._master_branch_cache = None |
|
344 |
if location: |
|
345 |
self._transport.put_bytes('bound', location+'\n', |
|
346 |
mode=self.bzrdir._get_file_mode()) |
|
347 |
else: |
|
348 |
try: |
|
349 |
self._transport.delete('bound') |
|
350 |
except errors.NoSuchFile: |
|
351 |
return False |
|
352 |
return True |
|
353 |
||
354 |
@needs_write_lock
|
|
355 |
def update(self, possible_transports=None): |
|
356 |
"""Synchronise this branch with the master branch if any. |
|
357 |
||
358 |
:return: None or the last_revision that was pivoted out during the
|
|
359 |
update.
|
|
360 |
"""
|
|
361 |
master = self.get_master_branch(possible_transports) |
|
362 |
if master is not None: |
|
363 |
old_tip = _mod_revision.ensure_null(self.last_revision()) |
|
364 |
self.pull(master, overwrite=True) |
|
365 |
if self.repository.get_graph().is_ancestor(old_tip, |
|
366 |
_mod_revision.ensure_null(self.last_revision())): |
|
367 |
return None |
|
368 |
return old_tip |
|
369 |
return None |
|
370 |
||
371 |
def _read_last_revision_info(self): |
|
372 |
revision_string = self._transport.get_bytes('last-revision') |
|
373 |
revno, revision_id = revision_string.rstrip('\n').split(' ', 1) |
|
374 |
revision_id = cache_utf8.get_cached_utf8(revision_id) |
|
375 |
revno = int(revno) |
|
376 |
return revno, revision_id |
|
377 |
||
378 |
def _write_last_revision_info(self, revno, revision_id): |
|
379 |
"""Simply write out the revision id, with no checks. |
|
380 |
||
381 |
Use set_last_revision_info to perform this safely.
|
|
382 |
||
383 |
Does not update the revision_history cache.
|
|
384 |
"""
|
|
385 |
revision_id = _mod_revision.ensure_null(revision_id) |
|
386 |
out_string = '%d %s\n' % (revno, revision_id) |
|
387 |
self._transport.put_bytes('last-revision', out_string, |
|
388 |
mode=self.bzrdir._get_file_mode()) |
|
389 |
||
390 |
@needs_write_lock
|
|
391 |
def update_feature_flags(self, updated_flags): |
|
392 |
"""Update the feature flags for this branch. |
|
393 |
||
394 |
:param updated_flags: Dictionary mapping feature names to necessities
|
|
395 |
A necessity can be None to indicate the feature should be removed
|
|
396 |
"""
|
|
397 |
self._format._update_feature_flags(updated_flags) |
|
398 |
self.control_transport.put_bytes('format', self._format.as_string()) |
|
399 |
||
400 |
||
401 |
class BzrBranch8(BzrBranch): |
|
402 |
"""A branch that stores tree-reference locations.""" |
|
403 |
||
404 |
def _open_hook(self, possible_transports=None): |
|
405 |
if self._ignore_fallbacks: |
|
406 |
return
|
|
407 |
if possible_transports is None: |
|
408 |
possible_transports = [self.bzrdir.root_transport] |
|
409 |
try: |
|
410 |
url = self.get_stacked_on_url() |
|
411 |
except (errors.UnstackableRepositoryFormat, errors.NotStacked, |
|
412 |
errors.UnstackableBranchFormat): |
|
413 |
pass
|
|
414 |
else: |
|
415 |
for hook in Branch.hooks['transform_fallback_location']: |
|
416 |
url = hook(self, url) |
|
417 |
if url is None: |
|
418 |
hook_name = Branch.hooks.get_hook_name(hook) |
|
419 |
raise AssertionError( |
|
420 |
"'transform_fallback_location' hook %s returned " |
|
421 |
"None, not a URL." % hook_name) |
|
422 |
self._activate_fallback_location(url, |
|
423 |
possible_transports=possible_transports) |
|
424 |
||
425 |
def __init__(self, *args, **kwargs): |
|
426 |
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False) |
|
427 |
super(BzrBranch8, self).__init__(*args, **kwargs) |
|
428 |
self._last_revision_info_cache = None |
|
429 |
self._reference_info = None |
|
430 |
||
431 |
def _clear_cached_state(self): |
|
432 |
super(BzrBranch8, self)._clear_cached_state() |
|
433 |
self._last_revision_info_cache = None |
|
434 |
self._reference_info = None |
|
435 |
||
436 |
def _check_history_violation(self, revision_id): |
|
437 |
current_revid = self.last_revision() |
|
438 |
last_revision = _mod_revision.ensure_null(current_revid) |
|
439 |
if _mod_revision.is_null(last_revision): |
|
440 |
return
|
|
441 |
graph = self.repository.get_graph() |
|
442 |
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id): |
|
443 |
if lh_ancestor == current_revid: |
|
444 |
return
|
|
445 |
raise errors.AppendRevisionsOnlyViolation(self.user_url) |
|
446 |
||
447 |
def _gen_revision_history(self): |
|
448 |
"""Generate the revision history from last revision |
|
449 |
"""
|
|
450 |
last_revno, last_revision = self.last_revision_info() |
|
451 |
self._extend_partial_history(stop_index=last_revno-1) |
|
452 |
return list(reversed(self._partial_revision_history_cache)) |
|
453 |
||
454 |
@needs_write_lock
|
|
455 |
def _set_parent_location(self, url): |
|
456 |
"""Set the parent branch""" |
|
457 |
self._set_config_location('parent_location', url, make_relative=True) |
|
458 |
||
459 |
@needs_read_lock
|
|
460 |
def _get_parent_location(self): |
|
461 |
"""Set the parent branch""" |
|
462 |
return self._get_config_location('parent_location') |
|
463 |
||
464 |
@needs_write_lock
|
|
465 |
def _set_all_reference_info(self, info_dict): |
|
466 |
"""Replace all reference info stored in a branch. |
|
467 |
||
468 |
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
|
|
469 |
"""
|
|
470 |
s = BytesIO() |
|
471 |
writer = rio.RioWriter(s) |
|
6653.1.8
by Jelmer Vernooij
Merge trunk. |
472 |
for key, (tree_path, branch_location) in viewitems(info_dict): |
6653.1.1
by Jelmer Vernooij
Split bzr branch code out into breezy.bzrbranch. |
473 |
stanza = rio.Stanza(file_id=key, tree_path=tree_path, |
474 |
branch_location=branch_location) |
|
475 |
writer.write_stanza(stanza) |
|
476 |
self._transport.put_bytes('references', s.getvalue()) |
|
477 |
self._reference_info = info_dict |
|
478 |
||
479 |
@needs_read_lock
|
|
480 |
def _get_all_reference_info(self): |
|
481 |
"""Return all the reference info stored in a branch. |
|
482 |
||
483 |
:return: A dict of {file_id: (tree_path, branch_location)}
|
|
484 |
"""
|
|
485 |
if self._reference_info is not None: |
|
486 |
return self._reference_info |
|
487 |
rio_file = self._transport.get('references') |
|
488 |
try: |
|
489 |
stanzas = rio.read_stanzas(rio_file) |
|
490 |
info_dict = dict((s['file_id'], (s['tree_path'], |
|
491 |
s['branch_location'])) for s in stanzas) |
|
492 |
finally: |
|
493 |
rio_file.close() |
|
494 |
self._reference_info = info_dict |
|
495 |
return info_dict |
|
496 |
||
497 |
def set_reference_info(self, file_id, tree_path, branch_location): |
|
498 |
"""Set the branch location to use for a tree reference. |
|
499 |
||
500 |
:param file_id: The file-id of the tree reference.
|
|
501 |
:param tree_path: The path of the tree reference in the tree.
|
|
502 |
:param branch_location: The location of the branch to retrieve tree
|
|
503 |
references from.
|
|
504 |
"""
|
|
505 |
info_dict = self._get_all_reference_info() |
|
506 |
info_dict[file_id] = (tree_path, branch_location) |
|
507 |
if None in (tree_path, branch_location): |
|
508 |
if tree_path is not None: |
|
509 |
raise ValueError('tree_path must be None when branch_location' |
|
510 |
' is None.') |
|
511 |
if branch_location is not None: |
|
512 |
raise ValueError('branch_location must be None when tree_path' |
|
513 |
' is None.') |
|
514 |
del info_dict[file_id] |
|
515 |
self._set_all_reference_info(info_dict) |
|
516 |
||
517 |
def get_reference_info(self, file_id): |
|
518 |
"""Get the tree_path and branch_location for a tree reference. |
|
519 |
||
520 |
:return: a tuple of (tree_path, branch_location)
|
|
521 |
"""
|
|
522 |
return self._get_all_reference_info().get(file_id, (None, None)) |
|
523 |
||
524 |
def reference_parent(self, file_id, path, possible_transports=None): |
|
525 |
"""Return the parent branch for a tree-reference file_id. |
|
526 |
||
527 |
:param file_id: The file_id of the tree reference
|
|
528 |
:param path: The path of the file_id in the tree
|
|
529 |
:return: A branch associated with the file_id
|
|
530 |
"""
|
|
531 |
branch_location = self.get_reference_info(file_id)[1] |
|
532 |
if branch_location is None: |
|
533 |
return Branch.reference_parent(self, file_id, path, |
|
534 |
possible_transports) |
|
535 |
branch_location = urlutils.join(self.user_url, branch_location) |
|
536 |
return Branch.open(branch_location, |
|
537 |
possible_transports=possible_transports) |
|
538 |
||
539 |
def set_push_location(self, location): |
|
540 |
"""See Branch.set_push_location.""" |
|
541 |
self._set_config_location('push_location', location) |
|
542 |
||
543 |
def set_bound_location(self, location): |
|
544 |
"""See Branch.set_push_location.""" |
|
545 |
self._master_branch_cache = None |
|
546 |
result = None |
|
547 |
conf = self.get_config_stack() |
|
548 |
if location is None: |
|
549 |
if not conf.get('bound'): |
|
550 |
return False |
|
551 |
else: |
|
552 |
conf.set('bound', 'False') |
|
553 |
return True |
|
554 |
else: |
|
555 |
self._set_config_location('bound_location', location, |
|
556 |
config=conf) |
|
557 |
conf.set('bound', 'True') |
|
558 |
return True |
|
559 |
||
560 |
def _get_bound_location(self, bound): |
|
561 |
"""Return the bound location in the config file. |
|
562 |
||
563 |
Return None if the bound parameter does not match"""
|
|
564 |
conf = self.get_config_stack() |
|
565 |
if conf.get('bound') != bound: |
|
566 |
return None |
|
567 |
return self._get_config_location('bound_location', config=conf) |
|
568 |
||
569 |
def get_bound_location(self): |
|
570 |
"""See Branch.get_bound_location.""" |
|
571 |
return self._get_bound_location(True) |
|
572 |
||
573 |
def get_old_bound_location(self): |
|
574 |
"""See Branch.get_old_bound_location""" |
|
575 |
return self._get_bound_location(False) |
|
576 |
||
577 |
def get_stacked_on_url(self): |
|
578 |
# you can always ask for the URL; but you might not be able to use it
|
|
579 |
# if the repo can't support stacking.
|
|
580 |
## self._check_stackable_repo()
|
|
581 |
# stacked_on_location is only ever defined in branch.conf, so don't
|
|
582 |
# waste effort reading the whole stack of config files.
|
|
583 |
conf = _mod_config.BranchOnlyStack(self) |
|
584 |
stacked_url = self._get_config_location('stacked_on_location', |
|
585 |
config=conf) |
|
586 |
if stacked_url is None: |
|
587 |
raise errors.NotStacked(self) |
|
588 |
return stacked_url.encode('utf-8') |
|
589 |
||
590 |
@needs_read_lock
|
|
591 |
def get_rev_id(self, revno, history=None): |
|
592 |
"""Find the revision id of the specified revno.""" |
|
593 |
if revno == 0: |
|
594 |
return _mod_revision.NULL_REVISION |
|
595 |
||
596 |
last_revno, last_revision_id = self.last_revision_info() |
|
597 |
if revno <= 0 or revno > last_revno: |
|
598 |
raise errors.NoSuchRevision(self, revno) |
|
599 |
||
600 |
if history is not None: |
|
601 |
return history[revno - 1] |
|
602 |
||
603 |
index = last_revno - revno |
|
604 |
if len(self._partial_revision_history_cache) <= index: |
|
605 |
self._extend_partial_history(stop_index=index) |
|
606 |
if len(self._partial_revision_history_cache) > index: |
|
607 |
return self._partial_revision_history_cache[index] |
|
608 |
else: |
|
609 |
raise errors.NoSuchRevision(self, revno) |
|
610 |
||
611 |
@needs_read_lock
|
|
612 |
def revision_id_to_revno(self, revision_id): |
|
613 |
"""Given a revision id, return its revno""" |
|
614 |
if _mod_revision.is_null(revision_id): |
|
615 |
return 0 |
|
616 |
try: |
|
617 |
index = self._partial_revision_history_cache.index(revision_id) |
|
618 |
except ValueError: |
|
619 |
try: |
|
620 |
self._extend_partial_history(stop_revision=revision_id) |
|
621 |
except errors.RevisionNotPresent as e: |
|
622 |
raise errors.GhostRevisionsHaveNoRevno(revision_id, e.revision_id) |
|
623 |
index = len(self._partial_revision_history_cache) - 1 |
|
624 |
if index < 0: |
|
625 |
raise errors.NoSuchRevision(self, revision_id) |
|
626 |
if self._partial_revision_history_cache[index] != revision_id: |
|
627 |
raise errors.NoSuchRevision(self, revision_id) |
|
628 |
return self.revno() - index |
|
629 |
||
630 |
||
631 |
class BzrBranch7(BzrBranch8): |
|
632 |
"""A branch with support for a fallback repository.""" |
|
633 |
||
634 |
def set_reference_info(self, file_id, tree_path, branch_location): |
|
635 |
Branch.set_reference_info(self, file_id, tree_path, branch_location) |
|
636 |
||
637 |
def get_reference_info(self, file_id): |
|
638 |
Branch.get_reference_info(self, file_id) |
|
639 |
||
640 |
def reference_parent(self, file_id, path, possible_transports=None): |
|
641 |
return Branch.reference_parent(self, file_id, path, |
|
642 |
possible_transports) |
|
643 |
||
644 |
||
645 |
class BzrBranch6(BzrBranch7): |
|
646 |
"""See BzrBranchFormat6 for the capabilities of this branch. |
|
647 |
||
648 |
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
|
|
649 |
i.e. stacking.
|
|
650 |
"""
|
|
651 |
||
652 |
def get_stacked_on_url(self): |
|
653 |
raise errors.UnstackableBranchFormat(self._format, self.user_url) |
|
654 |
||
655 |
||
656 |
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat): |
|
657 |
"""Base class for branch formats that live in meta directories. |
|
658 |
"""
|
|
659 |
||
660 |
def __init__(self): |
|
661 |
BranchFormat.__init__(self) |
|
662 |
bzrdir.BzrFormat.__init__(self) |
|
663 |
||
664 |
@classmethod
|
|
665 |
def find_format(klass, controldir, name=None): |
|
666 |
"""Return the format for the branch object in controldir.""" |
|
667 |
try: |
|
668 |
transport = controldir.get_branch_transport(None, name=name) |
|
669 |
except errors.NoSuchFile: |
|
670 |
raise errors.NotBranchError(path=name, bzrdir=controldir) |
|
671 |
try: |
|
672 |
format_string = transport.get_bytes("format") |
|
673 |
except errors.NoSuchFile: |
|
674 |
raise errors.NotBranchError(path=transport.base, bzrdir=controldir) |
|
675 |
return klass._find_format(format_registry, 'branch', format_string) |
|
676 |
||
677 |
def _branch_class(self): |
|
678 |
"""What class to instantiate on open calls.""" |
|
679 |
raise NotImplementedError(self._branch_class) |
|
680 |
||
681 |
def _get_initial_config(self, append_revisions_only=None): |
|
682 |
if append_revisions_only: |
|
683 |
return "append_revisions_only = True\n" |
|
684 |
else: |
|
685 |
# Avoid writing anything if append_revisions_only is disabled,
|
|
686 |
# as that is the default.
|
|
687 |
return "" |
|
688 |
||
689 |
def _initialize_helper(self, a_bzrdir, utf8_files, name=None, |
|
690 |
repository=None): |
|
691 |
"""Initialize a branch in a control dir, with specified files |
|
692 |
||
693 |
:param a_bzrdir: The bzrdir to initialize the branch in
|
|
694 |
:param utf8_files: The files to create as a list of
|
|
695 |
(filename, content) tuples
|
|
696 |
:param name: Name of colocated branch to create, if any
|
|
697 |
:return: a branch in this format
|
|
698 |
"""
|
|
699 |
if name is None: |
|
700 |
name = a_bzrdir._get_selected_branch() |
|
701 |
mutter('creating branch %r in %s', self, a_bzrdir.user_url) |
|
702 |
branch_transport = a_bzrdir.get_branch_transport(self, name=name) |
|
703 |
control_files = lockable_files.LockableFiles(branch_transport, |
|
704 |
'lock', lockdir.LockDir) |
|
705 |
control_files.create_lock() |
|
706 |
control_files.lock_write() |
|
707 |
try: |
|
708 |
utf8_files += [('format', self.as_string())] |
|
709 |
for (filename, content) in utf8_files: |
|
710 |
branch_transport.put_bytes( |
|
711 |
filename, content, |
|
712 |
mode=a_bzrdir._get_file_mode()) |
|
713 |
finally: |
|
714 |
control_files.unlock() |
|
715 |
branch = self.open(a_bzrdir, name, _found=True, |
|
716 |
found_repository=repository) |
|
717 |
self._run_post_branch_init_hooks(a_bzrdir, name, branch) |
|
718 |
return branch |
|
719 |
||
720 |
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False, |
|
721 |
found_repository=None, possible_transports=None): |
|
722 |
"""See BranchFormat.open().""" |
|
723 |
if name is None: |
|
724 |
name = a_bzrdir._get_selected_branch() |
|
725 |
if not _found: |
|
726 |
format = BranchFormatMetadir.find_format(a_bzrdir, name=name) |
|
727 |
if format.__class__ != self.__class__: |
|
728 |
raise AssertionError("wrong format %r found for %r" % |
|
729 |
(format, self)) |
|
730 |
transport = a_bzrdir.get_branch_transport(None, name=name) |
|
731 |
try: |
|
732 |
control_files = lockable_files.LockableFiles(transport, 'lock', |
|
733 |
lockdir.LockDir) |
|
734 |
if found_repository is None: |
|
735 |
found_repository = a_bzrdir.find_repository() |
|
736 |
return self._branch_class()(_format=self, |
|
737 |
_control_files=control_files, |
|
738 |
name=name, |
|
739 |
a_bzrdir=a_bzrdir, |
|
740 |
_repository=found_repository, |
|
741 |
ignore_fallbacks=ignore_fallbacks, |
|
742 |
possible_transports=possible_transports) |
|
743 |
except errors.NoSuchFile: |
|
744 |
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir) |
|
745 |
||
746 |
@property
|
|
747 |
def _matchingbzrdir(self): |
|
748 |
ret = bzrdir.BzrDirMetaFormat1() |
|
749 |
ret.set_branch_format(self) |
|
750 |
return ret |
|
751 |
||
752 |
def supports_tags(self): |
|
753 |
return True |
|
754 |
||
755 |
def supports_leaving_lock(self): |
|
756 |
return True |
|
757 |
||
758 |
def check_support_status(self, allow_unsupported, recommend_upgrade=True, |
|
759 |
basedir=None): |
|
760 |
BranchFormat.check_support_status(self, |
|
761 |
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade, |
|
762 |
basedir=basedir) |
|
763 |
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported, |
|
764 |
recommend_upgrade=recommend_upgrade, basedir=basedir) |
|
765 |
||
766 |
||
767 |
class BzrBranchFormat6(BranchFormatMetadir): |
|
768 |
"""Branch format with last-revision and tags. |
|
769 |
||
770 |
Unlike previous formats, this has no explicit revision history. Instead,
|
|
771 |
this just stores the last-revision, and the left-hand history leading
|
|
772 |
up to there is the history.
|
|
773 |
||
774 |
This format was introduced in bzr 0.15
|
|
775 |
and became the default in 0.91.
|
|
776 |
"""
|
|
777 |
||
778 |
def _branch_class(self): |
|
779 |
return BzrBranch6 |
|
780 |
||
781 |
@classmethod
|
|
782 |
def get_format_string(cls): |
|
783 |
"""See BranchFormat.get_format_string().""" |
|
784 |
return "Bazaar Branch Format 6 (bzr 0.15)\n" |
|
785 |
||
786 |
def get_format_description(self): |
|
787 |
"""See BranchFormat.get_format_description().""" |
|
788 |
return "Branch format 6" |
|
789 |
||
790 |
def initialize(self, a_bzrdir, name=None, repository=None, |
|
791 |
append_revisions_only=None): |
|
792 |
"""Create a branch of this format in a_bzrdir.""" |
|
793 |
utf8_files = [('last-revision', '0 null:\n'), |
|
794 |
('branch.conf', |
|
795 |
self._get_initial_config(append_revisions_only)), |
|
796 |
('tags', ''), |
|
797 |
]
|
|
798 |
return self._initialize_helper(a_bzrdir, utf8_files, name, repository) |
|
799 |
||
800 |
def make_tags(self, branch): |
|
801 |
"""See breezy.branch.BranchFormat.make_tags().""" |
|
802 |
return _mod_tag.BasicTags(branch) |
|
803 |
||
804 |
def supports_set_append_revisions_only(self): |
|
805 |
return True |
|
806 |
||
807 |
||
808 |
class BzrBranchFormat8(BranchFormatMetadir): |
|
809 |
"""Metadir format supporting storing locations of subtree branches.""" |
|
810 |
||
811 |
def _branch_class(self): |
|
812 |
return BzrBranch8 |
|
813 |
||
814 |
@classmethod
|
|
815 |
def get_format_string(cls): |
|
816 |
"""See BranchFormat.get_format_string().""" |
|
817 |
return "Bazaar Branch Format 8 (needs bzr 1.15)\n" |
|
818 |
||
819 |
def get_format_description(self): |
|
820 |
"""See BranchFormat.get_format_description().""" |
|
821 |
return "Branch format 8" |
|
822 |
||
823 |
def initialize(self, a_bzrdir, name=None, repository=None, |
|
824 |
append_revisions_only=None): |
|
825 |
"""Create a branch of this format in a_bzrdir.""" |
|
826 |
utf8_files = [('last-revision', '0 null:\n'), |
|
827 |
('branch.conf', |
|
828 |
self._get_initial_config(append_revisions_only)), |
|
829 |
('tags', ''), |
|
830 |
('references', '') |
|
831 |
]
|
|
832 |
return self._initialize_helper(a_bzrdir, utf8_files, name, repository) |
|
833 |
||
834 |
def make_tags(self, branch): |
|
835 |
"""See breezy.branch.BranchFormat.make_tags().""" |
|
836 |
return _mod_tag.BasicTags(branch) |
|
837 |
||
838 |
def supports_set_append_revisions_only(self): |
|
839 |
return True |
|
840 |
||
841 |
def supports_stacking(self): |
|
842 |
return True |
|
843 |
||
844 |
supports_reference_locations = True |
|
845 |
||
846 |
||
847 |
class BzrBranchFormat7(BranchFormatMetadir): |
|
848 |
"""Branch format with last-revision, tags, and a stacked location pointer. |
|
849 |
||
850 |
The stacked location pointer is passed down to the repository and requires
|
|
851 |
a repository format with supports_external_lookups = True.
|
|
852 |
||
853 |
This format was introduced in bzr 1.6.
|
|
854 |
"""
|
|
855 |
||
856 |
def initialize(self, a_bzrdir, name=None, repository=None, |
|
857 |
append_revisions_only=None): |
|
858 |
"""Create a branch of this format in a_bzrdir.""" |
|
859 |
utf8_files = [('last-revision', '0 null:\n'), |
|
860 |
('branch.conf', |
|
861 |
self._get_initial_config(append_revisions_only)), |
|
862 |
('tags', ''), |
|
863 |
]
|
|
864 |
return self._initialize_helper(a_bzrdir, utf8_files, name, repository) |
|
865 |
||
866 |
def _branch_class(self): |
|
867 |
return BzrBranch7 |
|
868 |
||
869 |
@classmethod
|
|
870 |
def get_format_string(cls): |
|
871 |
"""See BranchFormat.get_format_string().""" |
|
872 |
return "Bazaar Branch Format 7 (needs bzr 1.6)\n" |
|
873 |
||
874 |
def get_format_description(self): |
|
875 |
"""See BranchFormat.get_format_description().""" |
|
876 |
return "Branch format 7" |
|
877 |
||
878 |
def supports_set_append_revisions_only(self): |
|
879 |
return True |
|
880 |
||
881 |
def supports_stacking(self): |
|
882 |
return True |
|
883 |
||
884 |
def make_tags(self, branch): |
|
885 |
"""See breezy.branch.BranchFormat.make_tags().""" |
|
886 |
return _mod_tag.BasicTags(branch) |
|
887 |
||
888 |
supports_reference_locations = False |
|
889 |
||
890 |
||
891 |
class BranchReferenceFormat(BranchFormatMetadir): |
|
892 |
"""Bzr branch reference format. |
|
893 |
||
894 |
Branch references are used in implementing checkouts, they
|
|
895 |
act as an alias to the real branch which is at some other url.
|
|
896 |
||
897 |
This format has:
|
|
898 |
- A location file
|
|
899 |
- a format string
|
|
900 |
"""
|
|
901 |
||
902 |
@classmethod
|
|
903 |
def get_format_string(cls): |
|
904 |
"""See BranchFormat.get_format_string().""" |
|
905 |
return "Bazaar-NG Branch Reference Format 1\n" |
|
906 |
||
907 |
def get_format_description(self): |
|
908 |
"""See BranchFormat.get_format_description().""" |
|
909 |
return "Checkout reference format 1" |
|
910 |
||
911 |
def get_reference(self, a_bzrdir, name=None): |
|
912 |
"""See BranchFormat.get_reference().""" |
|
913 |
transport = a_bzrdir.get_branch_transport(None, name=name) |
|
914 |
return transport.get_bytes('location') |
|
915 |
||
916 |
def set_reference(self, a_bzrdir, name, to_branch): |
|
917 |
"""See BranchFormat.set_reference().""" |
|
918 |
transport = a_bzrdir.get_branch_transport(None, name=name) |
|
919 |
location = transport.put_bytes('location', to_branch.base) |
|
920 |
||
921 |
def initialize(self, a_bzrdir, name=None, target_branch=None, |
|
922 |
repository=None, append_revisions_only=None): |
|
923 |
"""Create a branch of this format in a_bzrdir.""" |
|
924 |
if target_branch is None: |
|
925 |
# this format does not implement branch itself, thus the implicit
|
|
926 |
# creation contract must see it as uninitializable
|
|
927 |
raise errors.UninitializableFormat(self) |
|
928 |
mutter('creating branch reference in %s', a_bzrdir.user_url) |
|
929 |
if a_bzrdir._format.fixed_components: |
|
930 |
raise errors.IncompatibleFormat(self, a_bzrdir._format) |
|
931 |
if name is None: |
|
932 |
name = a_bzrdir._get_selected_branch() |
|
933 |
branch_transport = a_bzrdir.get_branch_transport(self, name=name) |
|
934 |
branch_transport.put_bytes('location', |
|
935 |
target_branch.user_url) |
|
936 |
branch_transport.put_bytes('format', self.as_string()) |
|
937 |
branch = self.open(a_bzrdir, name, _found=True, |
|
938 |
possible_transports=[target_branch.bzrdir.root_transport]) |
|
939 |
self._run_post_branch_init_hooks(a_bzrdir, name, branch) |
|
940 |
return branch |
|
941 |
||
942 |
def _make_reference_clone_function(format, a_branch): |
|
943 |
"""Create a clone() routine for a branch dynamically.""" |
|
944 |
def clone(to_bzrdir, revision_id=None, |
|
945 |
repository_policy=None): |
|
946 |
"""See Branch.clone().""" |
|
947 |
return format.initialize(to_bzrdir, target_branch=a_branch) |
|
948 |
# cannot obey revision_id limits when cloning a reference ...
|
|
949 |
# FIXME RBC 20060210 either nuke revision_id for clone, or
|
|
950 |
# emit some sort of warning/error to the caller ?!
|
|
951 |
return clone |
|
952 |
||
953 |
def open(self, a_bzrdir, name=None, _found=False, location=None, |
|
954 |
possible_transports=None, ignore_fallbacks=False, |
|
955 |
found_repository=None): |
|
956 |
"""Return the branch that the branch reference in a_bzrdir points at. |
|
957 |
||
958 |
:param a_bzrdir: A BzrDir that contains a branch.
|
|
959 |
:param name: Name of colocated branch to open, if any
|
|
960 |
:param _found: a private parameter, do not use it. It is used to
|
|
961 |
indicate if format probing has already be done.
|
|
962 |
:param ignore_fallbacks: when set, no fallback branches will be opened
|
|
963 |
(if there are any). Default is to open fallbacks.
|
|
964 |
:param location: The location of the referenced branch. If
|
|
965 |
unspecified, this will be determined from the branch reference in
|
|
966 |
a_bzrdir.
|
|
967 |
:param possible_transports: An optional reusable transports list.
|
|
968 |
"""
|
|
969 |
if name is None: |
|
970 |
name = a_bzrdir._get_selected_branch() |
|
971 |
if not _found: |
|
972 |
format = BranchFormatMetadir.find_format(a_bzrdir, name=name) |
|
973 |
if format.__class__ != self.__class__: |
|
974 |
raise AssertionError("wrong format %r found for %r" % |
|
975 |
(format, self)) |
|
976 |
if location is None: |
|
977 |
location = self.get_reference(a_bzrdir, name) |
|
978 |
real_bzrdir = controldir.ControlDir.open( |
|
979 |
location, possible_transports=possible_transports) |
|
980 |
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks, |
|
981 |
possible_transports=possible_transports) |
|
982 |
# this changes the behaviour of result.clone to create a new reference
|
|
983 |
# rather than a copy of the content of the branch.
|
|
984 |
# I did not use a proxy object because that needs much more extensive
|
|
985 |
# testing, and we are only changing one behaviour at the moment.
|
|
986 |
# If we decide to alter more behaviours - i.e. the implicit nickname
|
|
987 |
# then this should be refactored to introduce a tested proxy branch
|
|
988 |
# and a subclass of that for use in overriding clone() and ....
|
|
989 |
# - RBC 20060210
|
|
990 |
result.clone = self._make_reference_clone_function(result) |
|
991 |
return result |
|
992 |
||
993 |
||
994 |
class Converter5to6(object): |
|
995 |
"""Perform an in-place upgrade of format 5 to format 6""" |
|
996 |
||
997 |
def convert(self, branch): |
|
998 |
# Data for 5 and 6 can peacefully coexist.
|
|
999 |
format = BzrBranchFormat6() |
|
1000 |
new_branch = format.open(branch.bzrdir, _found=True) |
|
1001 |
||
1002 |
# Copy source data into target
|
|
1003 |
new_branch._write_last_revision_info(*branch.last_revision_info()) |
|
1004 |
new_branch.lock_write() |
|
1005 |
try: |
|
1006 |
new_branch.set_parent(branch.get_parent()) |
|
1007 |
new_branch.set_bound_location(branch.get_bound_location()) |
|
1008 |
new_branch.set_push_location(branch.get_push_location()) |
|
1009 |
finally: |
|
1010 |
new_branch.unlock() |
|
1011 |
||
1012 |
# New branch has no tags by default
|
|
1013 |
new_branch.tags._set_tag_dict({}) |
|
1014 |
||
1015 |
# Copying done; now update target format
|
|
1016 |
new_branch._transport.put_bytes('format', |
|
1017 |
format.as_string(), |
|
1018 |
mode=new_branch.bzrdir._get_file_mode()) |
|
1019 |
||
1020 |
# Clean up old files
|
|
1021 |
new_branch._transport.delete('revision-history') |
|
1022 |
branch.lock_write() |
|
1023 |
try: |
|
1024 |
try: |
|
1025 |
branch.set_parent(None) |
|
1026 |
except errors.NoSuchFile: |
|
1027 |
pass
|
|
1028 |
branch.set_bound_location(None) |
|
1029 |
finally: |
|
1030 |
branch.unlock() |
|
1031 |
||
1032 |
||
1033 |
class Converter6to7(object): |
|
1034 |
"""Perform an in-place upgrade of format 6 to format 7""" |
|
1035 |
||
1036 |
def convert(self, branch): |
|
1037 |
format = BzrBranchFormat7() |
|
1038 |
branch._set_config_location('stacked_on_location', '') |
|
1039 |
# update target format
|
|
1040 |
branch._transport.put_bytes('format', format.as_string()) |
|
1041 |
||
1042 |
||
1043 |
class Converter7to8(object): |
|
1044 |
"""Perform an in-place upgrade of format 7 to format 8""" |
|
1045 |
||
1046 |
def convert(self, branch): |
|
1047 |
format = BzrBranchFormat8() |
|
1048 |
branch._transport.put_bytes('references', '') |
|
1049 |
# update target format
|
|
1050 |
branch._transport.put_bytes('format', format.as_string()) |
|
1051 |
||
1052 |
||
1053 |