/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: Martin
  • Date: 2017-06-18 10:15:11 UTC
  • mto: This revision was merged to the branch mainline in revision 6715.
  • Revision ID: gzlist@googlemail.com-20170618101511-fri1mouxt1hc09r8
Make _simple_set tests pass on py3 and with random hash

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