/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: Jelmer Vernooij
  • Date: 2020-01-24 00:06:05 UTC
  • mto: This revision was merged to the branch mainline in revision 7459.
  • Revision ID: jelmer@jelmer.uk-20200124000605-qw2v9i7pjfrcy12m
Support importing Git submodules as tree references.

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