/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/bzr/branch.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-08-23 01:15:41 UTC
  • mfrom: (7520.1.4 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200823011541-nv0oh7nzaganx2qy
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/389690

Show diffs side-by-side

added added

removed removed

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