/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: 2018-03-10 13:18:10 UTC
  • mto: This revision was merged to the branch mainline in revision 6893.
  • Revision ID: jelmer@jelmer.uk-20180310131810-iiblggbkb757eopm
Avoid call to has_id.

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