/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/bzrbranch.py

  • Committer: Martin
  • Date: 2017-06-10 01:57:00 UTC
  • mto: This revision was merged to the branch mainline in revision 6679.
  • Revision ID: gzlist@googlemail.com-20170610015700-o3xeuyaqry2obiay
Go back to native str for urls and many other py3 changes

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 (
 
34
    bzrdir,
 
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_bzrdir=None, name=None,
 
79
                 _repository=None, ignore_fallbacks=False,
 
80
                 possible_transports=None):
 
81
        """Create new branch object at a particular location."""
 
82
        if a_bzrdir is None:
 
83
            raise ValueError('a_bzrdir must be supplied')
 
84
        if name is None:
 
85
            raise ValueError('name must be supplied')
 
86
        self.bzrdir = a_bzrdir
 
87
        self._user_transport = self.bzrdir.transport.clone('..')
 
88
        if name != "":
 
89
            self._user_transport.set_segment_parameter(
 
90
                "branch", urlutils.escape(name))
 
91
        self._base = self._user_transport.base
 
92
        self.name = name
 
93
        self._format = _format
 
94
        if _control_files is None:
 
95
            raise ValueError('BzrBranch _control_files is None')
 
96
        self.control_files = _control_files
 
97
        self._transport = _control_files._transport
 
98
        self.repository = _repository
 
99
        self.conf_store = None
 
100
        Branch.__init__(self, possible_transports)
 
101
 
 
102
    def __str__(self):
 
103
        return '%s(%s)' % (self.__class__.__name__, self.user_url)
 
104
 
 
105
    __repr__ = __str__
 
106
 
 
107
    def _get_base(self):
 
108
        """Returns the directory containing the control directory."""
 
109
        return self._base
 
110
 
 
111
    base = property(_get_base, doc="The URL for the root of this branch.")
 
112
 
 
113
    @property
 
114
    def user_transport(self):
 
115
        return self._user_transport
 
116
 
 
117
    def _get_config(self):
 
118
        return _mod_config.TransportConfig(self._transport, 'branch.conf')
 
119
 
 
120
    def _get_config_store(self):
 
121
        if self.conf_store is None:
 
122
            self.conf_store =  _mod_config.BranchStore(self)
 
123
        return self.conf_store
 
124
 
 
125
    def _uncommitted_branch(self):
 
126
        """Return the branch that may contain uncommitted changes."""
 
127
        master = self.get_master_branch()
 
128
        if master is not None:
 
129
            return master
 
130
        else:
 
131
            return self
 
132
 
 
133
    def store_uncommitted(self, creator):
 
134
        """Store uncommitted changes from a ShelfCreator.
 
135
 
 
136
        :param creator: The ShelfCreator containing uncommitted changes, or
 
137
            None to delete any stored changes.
 
138
        :raises: ChangesAlreadyStored if the branch already has changes.
 
139
        """
 
140
        branch = self._uncommitted_branch()
 
141
        if creator is None:
 
142
            branch._transport.delete('stored-transform')
 
143
            return
 
144
        if branch._transport.has('stored-transform'):
 
145
            raise errors.ChangesAlreadyStored
 
146
        transform = BytesIO()
 
147
        creator.write_shelf(transform)
 
148
        transform.seek(0)
 
149
        branch._transport.put_file('stored-transform', transform)
 
150
 
 
151
    def get_unshelver(self, tree):
 
152
        """Return a shelf.Unshelver for this branch and tree.
 
153
 
 
154
        :param tree: The tree to use to construct the Unshelver.
 
155
        :return: an Unshelver or None if no changes are stored.
 
156
        """
 
157
        branch = self._uncommitted_branch()
 
158
        try:
 
159
            transform = branch._transport.get('stored-transform')
 
160
        except errors.NoSuchFile:
 
161
            return None
 
162
        return shelf.Unshelver.from_tree_and_shelf(tree, transform)
 
163
 
 
164
    def is_locked(self):
 
165
        return self.control_files.is_locked()
 
166
 
 
167
    def lock_write(self, token=None):
 
168
        """Lock the branch for write operations.
 
169
 
 
170
        :param token: A token to permit reacquiring a previously held and
 
171
            preserved lock.
 
172
        :return: A BranchWriteLockResult.
 
173
        """
 
174
        if not self.is_locked():
 
175
            self._note_lock('w')
 
176
            self.repository._warn_if_deprecated(self)
 
177
            self.repository.lock_write()
 
178
            took_lock = True
 
179
        else:
 
180
            took_lock = False
 
181
        try:
 
182
            return BranchWriteLockResult(self.unlock,
 
183
                self.control_files.lock_write(token=token))
 
184
        except:
 
185
            if took_lock:
 
186
                self.repository.unlock()
 
187
            raise
 
188
 
 
189
    def lock_read(self):
 
190
        """Lock the branch for read operations.
 
191
 
 
192
        :return: A breezy.lock.LogicalLockResult.
 
193
        """
 
194
        if not self.is_locked():
 
195
            self._note_lock('r')
 
196
            self.repository._warn_if_deprecated(self)
 
197
            self.repository.lock_read()
 
198
            took_lock = True
 
199
        else:
 
200
            took_lock = False
 
201
        try:
 
202
            self.control_files.lock_read()
 
203
            return LogicalLockResult(self.unlock)
 
204
        except:
 
205
            if took_lock:
 
206
                self.repository.unlock()
 
207
            raise
 
208
 
 
209
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
210
    def unlock(self):
 
211
        if self.control_files._lock_count == 1 and self.conf_store is not None:
 
212
            self.conf_store.save_changes()
 
213
        try:
 
214
            self.control_files.unlock()
 
215
        finally:
 
216
            if not self.control_files.is_locked():
 
217
                self.repository.unlock()
 
218
                # we just released the lock
 
219
                self._clear_cached_state()
 
220
 
 
221
    def peek_lock_mode(self):
 
222
        if self.control_files._lock_count == 0:
 
223
            return None
 
224
        else:
 
225
            return self.control_files._lock_mode
 
226
 
 
227
    def get_physical_lock_status(self):
 
228
        return self.control_files.get_physical_lock_status()
 
229
 
 
230
    @needs_read_lock
 
231
    def print_file(self, file, revision_id):
 
232
        """See Branch.print_file."""
 
233
        return self.repository.print_file(file, revision_id)
 
234
 
 
235
    @needs_write_lock
 
236
    def set_last_revision_info(self, revno, revision_id):
 
237
        if not revision_id or not isinstance(revision_id, basestring):
 
238
            raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
 
239
        revision_id = _mod_revision.ensure_null(revision_id)
 
240
        old_revno, old_revid = self.last_revision_info()
 
241
        if self.get_append_revisions_only():
 
242
            self._check_history_violation(revision_id)
 
243
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
244
        self._write_last_revision_info(revno, revision_id)
 
245
        self._clear_cached_state()
 
246
        self._last_revision_info_cache = revno, revision_id
 
247
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
248
 
 
249
    def basis_tree(self):
 
250
        """See Branch.basis_tree."""
 
251
        return self.repository.revision_tree(self.last_revision())
 
252
 
 
253
    def _get_parent_location(self):
 
254
        _locs = ['parent', 'pull', 'x-pull']
 
255
        for l in _locs:
 
256
            try:
 
257
                return self._transport.get_bytes(l).strip('\n')
 
258
            except errors.NoSuchFile:
 
259
                pass
 
260
        return None
 
261
 
 
262
    def get_stacked_on_url(self):
 
263
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
 
264
 
 
265
    def set_push_location(self, location):
 
266
        """See Branch.set_push_location."""
 
267
        self.get_config().set_user_option(
 
268
            'push_location', location,
 
269
            store=_mod_config.STORE_LOCATION_NORECURSE)
 
270
 
 
271
    def _set_parent_location(self, url):
 
272
        if url is None:
 
273
            self._transport.delete('parent')
 
274
        else:
 
275
            self._transport.put_bytes('parent', url + '\n',
 
276
                mode=self.bzrdir._get_file_mode())
 
277
 
 
278
    @needs_write_lock
 
279
    def unbind(self):
 
280
        """If bound, unbind"""
 
281
        return self.set_bound_location(None)
 
282
 
 
283
    @needs_write_lock
 
284
    def bind(self, other):
 
285
        """Bind this branch to the branch other.
 
286
 
 
287
        This does not push or pull data between the branches, though it does
 
288
        check for divergence to raise an error when the branches are not
 
289
        either the same, or one a prefix of the other. That behaviour may not
 
290
        be useful, so that check may be removed in future.
 
291
 
 
292
        :param other: The branch to bind to
 
293
        :type other: Branch
 
294
        """
 
295
        # TODO: jam 20051230 Consider checking if the target is bound
 
296
        #       It is debatable whether you should be able to bind to
 
297
        #       a branch which is itself bound.
 
298
        #       Committing is obviously forbidden,
 
299
        #       but binding itself may not be.
 
300
        #       Since we *have* to check at commit time, we don't
 
301
        #       *need* to check here
 
302
 
 
303
        # we want to raise diverged if:
 
304
        # last_rev is not in the other_last_rev history, AND
 
305
        # other_last_rev is not in our history, and do it without pulling
 
306
        # history around
 
307
        self.set_bound_location(other.base)
 
308
 
 
309
    def get_bound_location(self):
 
310
        try:
 
311
            return self._transport.get_bytes('bound')[:-1]
 
312
        except errors.NoSuchFile:
 
313
            return None
 
314
 
 
315
    @needs_read_lock
 
316
    def get_master_branch(self, possible_transports=None):
 
317
        """Return the branch we are bound to.
 
318
 
 
319
        :return: Either a Branch, or None
 
320
        """
 
321
        if self._master_branch_cache is None:
 
322
            self._master_branch_cache = self._get_master_branch(
 
323
                possible_transports)
 
324
        return self._master_branch_cache
 
325
 
 
326
    def _get_master_branch(self, possible_transports):
 
327
        bound_loc = self.get_bound_location()
 
328
        if not bound_loc:
 
329
            return None
 
330
        try:
 
331
            return Branch.open(bound_loc,
 
332
                               possible_transports=possible_transports)
 
333
        except (errors.NotBranchError, errors.ConnectionError) as e:
 
334
            raise errors.BoundBranchConnectionFailure(
 
335
                    self, bound_loc, e)
 
336
 
 
337
    @needs_write_lock
 
338
    def set_bound_location(self, location):
 
339
        """Set the target where this branch is bound to.
 
340
 
 
341
        :param location: URL to the target branch
 
342
        """
 
343
        self._master_branch_cache = None
 
344
        if location:
 
345
            self._transport.put_bytes('bound', location+'\n',
 
346
                mode=self.bzrdir._get_file_mode())
 
347
        else:
 
348
            try:
 
349
                self._transport.delete('bound')
 
350
            except errors.NoSuchFile:
 
351
                return False
 
352
            return True
 
353
 
 
354
    @needs_write_lock
 
355
    def update(self, possible_transports=None):
 
356
        """Synchronise this branch with the master branch if any.
 
357
 
 
358
        :return: None or the last_revision that was pivoted out during the
 
359
                 update.
 
360
        """
 
361
        master = self.get_master_branch(possible_transports)
 
362
        if master is not None:
 
363
            old_tip = _mod_revision.ensure_null(self.last_revision())
 
364
            self.pull(master, overwrite=True)
 
365
            if self.repository.get_graph().is_ancestor(old_tip,
 
366
                _mod_revision.ensure_null(self.last_revision())):
 
367
                return None
 
368
            return old_tip
 
369
        return None
 
370
 
 
371
    def _read_last_revision_info(self):
 
372
        revision_string = self._transport.get_bytes('last-revision')
 
373
        revno, revision_id = revision_string.rstrip(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.bzrdir._get_file_mode())
 
389
 
 
390
    @needs_write_lock
 
391
    def update_feature_flags(self, updated_flags):
 
392
        """Update the feature flags for this branch.
 
393
 
 
394
        :param updated_flags: Dictionary mapping feature names to necessities
 
395
            A necessity can be None to indicate the feature should be removed
 
396
        """
 
397
        self._format._update_feature_flags(updated_flags)
 
398
        self.control_transport.put_bytes('format', self._format.as_string())
 
399
 
 
400
 
 
401
class BzrBranch8(BzrBranch):
 
402
    """A branch that stores tree-reference locations."""
 
403
 
 
404
    def _open_hook(self, possible_transports=None):
 
405
        if self._ignore_fallbacks:
 
406
            return
 
407
        if possible_transports is None:
 
408
            possible_transports = [self.bzrdir.root_transport]
 
409
        try:
 
410
            url = self.get_stacked_on_url()
 
411
        except (errors.UnstackableRepositoryFormat, errors.NotStacked,
 
412
            errors.UnstackableBranchFormat):
 
413
            pass
 
414
        else:
 
415
            for hook in Branch.hooks['transform_fallback_location']:
 
416
                url = hook(self, url)
 
417
                if url is None:
 
418
                    hook_name = Branch.hooks.get_hook_name(hook)
 
419
                    raise AssertionError(
 
420
                        "'transform_fallback_location' hook %s returned "
 
421
                        "None, not a URL." % hook_name)
 
422
            self._activate_fallback_location(url,
 
423
                possible_transports=possible_transports)
 
424
 
 
425
    def __init__(self, *args, **kwargs):
 
426
        self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
 
427
        super(BzrBranch8, self).__init__(*args, **kwargs)
 
428
        self._last_revision_info_cache = None
 
429
        self._reference_info = None
 
430
 
 
431
    def _clear_cached_state(self):
 
432
        super(BzrBranch8, self)._clear_cached_state()
 
433
        self._last_revision_info_cache = None
 
434
        self._reference_info = None
 
435
 
 
436
    def _check_history_violation(self, revision_id):
 
437
        current_revid = self.last_revision()
 
438
        last_revision = _mod_revision.ensure_null(current_revid)
 
439
        if _mod_revision.is_null(last_revision):
 
440
            return
 
441
        graph = self.repository.get_graph()
 
442
        for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
 
443
            if lh_ancestor == current_revid:
 
444
                return
 
445
        raise errors.AppendRevisionsOnlyViolation(self.user_url)
 
446
 
 
447
    def _gen_revision_history(self):
 
448
        """Generate the revision history from last revision
 
449
        """
 
450
        last_revno, last_revision = self.last_revision_info()
 
451
        self._extend_partial_history(stop_index=last_revno-1)
 
452
        return list(reversed(self._partial_revision_history_cache))
 
453
 
 
454
    @needs_write_lock
 
455
    def _set_parent_location(self, url):
 
456
        """Set the parent branch"""
 
457
        self._set_config_location('parent_location', url, make_relative=True)
 
458
 
 
459
    @needs_read_lock
 
460
    def _get_parent_location(self):
 
461
        """Set the parent branch"""
 
462
        return self._get_config_location('parent_location')
 
463
 
 
464
    @needs_write_lock
 
465
    def _set_all_reference_info(self, info_dict):
 
466
        """Replace all reference info stored in a branch.
 
467
 
 
468
        :param info_dict: A dict of {file_id: (tree_path, branch_location)}
 
469
        """
 
470
        s = BytesIO()
 
471
        writer = rio.RioWriter(s)
 
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, bzrdir=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(path=transport.base, bzrdir=controldir)
 
677
        return klass._find_format(format_registry, 'branch', format_text)
 
678
 
 
679
    def _branch_class(self):
 
680
        """What class to instantiate on open calls."""
 
681
        raise NotImplementedError(self._branch_class)
 
682
 
 
683
    def _get_initial_config(self, append_revisions_only=None):
 
684
        if append_revisions_only:
 
685
            return b"append_revisions_only = True\n"
 
686
        else:
 
687
            # Avoid writing anything if append_revisions_only is disabled,
 
688
            # as that is the default.
 
689
            return b""
 
690
 
 
691
    def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
 
692
                           repository=None):
 
693
        """Initialize a branch in a control dir, with specified files
 
694
 
 
695
        :param a_bzrdir: The bzrdir to initialize the branch in
 
696
        :param utf8_files: The files to create as a list of
 
697
            (filename, content) tuples
 
698
        :param name: Name of colocated branch to create, if any
 
699
        :return: a branch in this format
 
700
        """
 
701
        if name is None:
 
702
            name = a_bzrdir._get_selected_branch()
 
703
        mutter('creating branch %r in %s', self, a_bzrdir.user_url)
 
704
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
 
705
        control_files = lockable_files.LockableFiles(branch_transport,
 
706
            'lock', lockdir.LockDir)
 
707
        control_files.create_lock()
 
708
        control_files.lock_write()
 
709
        try:
 
710
            utf8_files += [('format', self.as_string())]
 
711
            for (filename, content) in utf8_files:
 
712
                branch_transport.put_bytes(
 
713
                    filename, content,
 
714
                    mode=a_bzrdir._get_file_mode())
 
715
        finally:
 
716
            control_files.unlock()
 
717
        branch = self.open(a_bzrdir, name, _found=True,
 
718
                found_repository=repository)
 
719
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
720
        return branch
 
721
 
 
722
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
 
723
            found_repository=None, possible_transports=None):
 
724
        """See BranchFormat.open()."""
 
725
        if name is None:
 
726
            name = a_bzrdir._get_selected_branch()
 
727
        if not _found:
 
728
            format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
 
729
            if format.__class__ != self.__class__:
 
730
                raise AssertionError("wrong format %r found for %r" %
 
731
                    (format, self))
 
732
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
733
        try:
 
734
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
735
                                                         lockdir.LockDir)
 
736
            if found_repository is None:
 
737
                found_repository = a_bzrdir.find_repository()
 
738
            return self._branch_class()(_format=self,
 
739
                              _control_files=control_files,
 
740
                              name=name,
 
741
                              a_bzrdir=a_bzrdir,
 
742
                              _repository=found_repository,
 
743
                              ignore_fallbacks=ignore_fallbacks,
 
744
                              possible_transports=possible_transports)
 
745
        except errors.NoSuchFile:
 
746
            raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
 
747
 
 
748
    @property
 
749
    def _matchingbzrdir(self):
 
750
        ret = bzrdir.BzrDirMetaFormat1()
 
751
        ret.set_branch_format(self)
 
752
        return ret
 
753
 
 
754
    def supports_tags(self):
 
755
        return True
 
756
 
 
757
    def supports_leaving_lock(self):
 
758
        return True
 
759
 
 
760
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
761
            basedir=None):
 
762
        BranchFormat.check_support_status(self,
 
763
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
764
            basedir=basedir)
 
765
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
766
            recommend_upgrade=recommend_upgrade, basedir=basedir)
 
767
 
 
768
 
 
769
class BzrBranchFormat6(BranchFormatMetadir):
 
770
    """Branch format with last-revision and tags.
 
771
 
 
772
    Unlike previous formats, this has no explicit revision history. Instead,
 
773
    this just stores the last-revision, and the left-hand history leading
 
774
    up to there is the history.
 
775
 
 
776
    This format was introduced in bzr 0.15
 
777
    and became the default in 0.91.
 
778
    """
 
779
 
 
780
    def _branch_class(self):
 
781
        return BzrBranch6
 
782
 
 
783
    @classmethod
 
784
    def get_format_string(cls):
 
785
        """See BranchFormat.get_format_string()."""
 
786
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
 
787
 
 
788
    def get_format_description(self):
 
789
        """See BranchFormat.get_format_description()."""
 
790
        return "Branch format 6"
 
791
 
 
792
    def initialize(self, a_bzrdir, name=None, repository=None,
 
793
                   append_revisions_only=None):
 
794
        """Create a branch of this format in a_bzrdir."""
 
795
        utf8_files = [('last-revision', '0 null:\n'),
 
796
                      ('branch.conf',
 
797
                          self._get_initial_config(append_revisions_only)),
 
798
                      ('tags', ''),
 
799
                      ]
 
800
        return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
 
801
 
 
802
    def make_tags(self, branch):
 
803
        """See breezy.branch.BranchFormat.make_tags()."""
 
804
        return _mod_tag.BasicTags(branch)
 
805
 
 
806
    def supports_set_append_revisions_only(self):
 
807
        return True
 
808
 
 
809
 
 
810
class BzrBranchFormat8(BranchFormatMetadir):
 
811
    """Metadir format supporting storing locations of subtree branches."""
 
812
 
 
813
    def _branch_class(self):
 
814
        return BzrBranch8
 
815
 
 
816
    @classmethod
 
817
    def get_format_string(cls):
 
818
        """See BranchFormat.get_format_string()."""
 
819
        return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
 
820
 
 
821
    def get_format_description(self):
 
822
        """See BranchFormat.get_format_description()."""
 
823
        return "Branch format 8"
 
824
 
 
825
    def initialize(self, a_bzrdir, name=None, repository=None,
 
826
                   append_revisions_only=None):
 
827
        """Create a branch of this format in a_bzrdir."""
 
828
        utf8_files = [('last-revision', '0 null:\n'),
 
829
                      ('branch.conf',
 
830
                          self._get_initial_config(append_revisions_only)),
 
831
                      ('tags', ''),
 
832
                      ('references', '')
 
833
                      ]
 
834
        return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
 
835
 
 
836
    def make_tags(self, branch):
 
837
        """See breezy.branch.BranchFormat.make_tags()."""
 
838
        return _mod_tag.BasicTags(branch)
 
839
 
 
840
    def supports_set_append_revisions_only(self):
 
841
        return True
 
842
 
 
843
    def supports_stacking(self):
 
844
        return True
 
845
 
 
846
    supports_reference_locations = True
 
847
 
 
848
 
 
849
class BzrBranchFormat7(BranchFormatMetadir):
 
850
    """Branch format with last-revision, tags, and a stacked location pointer.
 
851
 
 
852
    The stacked location pointer is passed down to the repository and requires
 
853
    a repository format with supports_external_lookups = True.
 
854
 
 
855
    This format was introduced in bzr 1.6.
 
856
    """
 
857
 
 
858
    def initialize(self, a_bzrdir, name=None, repository=None,
 
859
                   append_revisions_only=None):
 
860
        """Create a branch of this format in a_bzrdir."""
 
861
        utf8_files = [('last-revision', b'0 null:\n'),
 
862
                      ('branch.conf',
 
863
                          self._get_initial_config(append_revisions_only)),
 
864
                      ('tags', b''),
 
865
                      ]
 
866
        return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
 
867
 
 
868
    def _branch_class(self):
 
869
        return BzrBranch7
 
870
 
 
871
    @classmethod
 
872
    def get_format_string(cls):
 
873
        """See BranchFormat.get_format_string()."""
 
874
        return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
 
875
 
 
876
    def get_format_description(self):
 
877
        """See BranchFormat.get_format_description()."""
 
878
        return "Branch format 7"
 
879
 
 
880
    def supports_set_append_revisions_only(self):
 
881
        return True
 
882
 
 
883
    def supports_stacking(self):
 
884
        return True
 
885
 
 
886
    def make_tags(self, branch):
 
887
        """See breezy.branch.BranchFormat.make_tags()."""
 
888
        return _mod_tag.BasicTags(branch)
 
889
 
 
890
    supports_reference_locations = False
 
891
 
 
892
 
 
893
class BranchReferenceFormat(BranchFormatMetadir):
 
894
    """Bzr branch reference format.
 
895
 
 
896
    Branch references are used in implementing checkouts, they
 
897
    act as an alias to the real branch which is at some other url.
 
898
 
 
899
    This format has:
 
900
     - A location file
 
901
     - a format string
 
902
    """
 
903
 
 
904
    @classmethod
 
905
    def get_format_string(cls):
 
906
        """See BranchFormat.get_format_string()."""
 
907
        return "Bazaar-NG Branch Reference Format 1\n"
 
908
 
 
909
    def get_format_description(self):
 
910
        """See BranchFormat.get_format_description()."""
 
911
        return "Checkout reference format 1"
 
912
 
 
913
    def get_reference(self, a_bzrdir, name=None):
 
914
        """See BranchFormat.get_reference()."""
 
915
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
916
        return transport.get_bytes('location')
 
917
 
 
918
    def set_reference(self, a_bzrdir, name, to_branch):
 
919
        """See BranchFormat.set_reference()."""
 
920
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
921
        location = transport.put_bytes('location', to_branch.base)
 
922
 
 
923
    def initialize(self, a_bzrdir, name=None, target_branch=None,
 
924
            repository=None, append_revisions_only=None):
 
925
        """Create a branch of this format in a_bzrdir."""
 
926
        if target_branch is None:
 
927
            # this format does not implement branch itself, thus the implicit
 
928
            # creation contract must see it as uninitializable
 
929
            raise errors.UninitializableFormat(self)
 
930
        mutter('creating branch reference in %s', a_bzrdir.user_url)
 
931
        if a_bzrdir._format.fixed_components:
 
932
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
933
        if name is None:
 
934
            name = a_bzrdir._get_selected_branch()
 
935
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
 
936
        branch_transport.put_bytes('location',
 
937
            target_branch.user_url)
 
938
        branch_transport.put_bytes('format', self.as_string())
 
939
        branch = self.open(a_bzrdir, name, _found=True,
 
940
            possible_transports=[target_branch.bzrdir.root_transport])
 
941
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
942
        return branch
 
943
 
 
944
    def _make_reference_clone_function(format, a_branch):
 
945
        """Create a clone() routine for a branch dynamically."""
 
946
        def clone(to_bzrdir, revision_id=None,
 
947
            repository_policy=None):
 
948
            """See Branch.clone()."""
 
949
            return format.initialize(to_bzrdir, target_branch=a_branch)
 
950
            # cannot obey revision_id limits when cloning a reference ...
 
951
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
952
            # emit some sort of warning/error to the caller ?!
 
953
        return clone
 
954
 
 
955
    def open(self, a_bzrdir, name=None, _found=False, location=None,
 
956
             possible_transports=None, ignore_fallbacks=False,
 
957
             found_repository=None):
 
958
        """Return the branch that the branch reference in a_bzrdir points at.
 
959
 
 
960
        :param a_bzrdir: A BzrDir that contains a branch.
 
961
        :param name: Name of colocated branch to open, if any
 
962
        :param _found: a private parameter, do not use it. It is used to
 
963
            indicate if format probing has already be done.
 
964
        :param ignore_fallbacks: when set, no fallback branches will be opened
 
965
            (if there are any).  Default is to open fallbacks.
 
966
        :param location: The location of the referenced branch.  If
 
967
            unspecified, this will be determined from the branch reference in
 
968
            a_bzrdir.
 
969
        :param possible_transports: An optional reusable transports list.
 
970
        """
 
971
        if name is None:
 
972
            name = a_bzrdir._get_selected_branch()
 
973
        if not _found:
 
974
            format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
 
975
            if format.__class__ != self.__class__:
 
976
                raise AssertionError("wrong format %r found for %r" %
 
977
                    (format, self))
 
978
        if location is None:
 
979
            location = self.get_reference(a_bzrdir, name)
 
980
        real_bzrdir = controldir.ControlDir.open(
 
981
            location, possible_transports=possible_transports)
 
982
        result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
 
983
            possible_transports=possible_transports)
 
984
        # this changes the behaviour of result.clone to create a new reference
 
985
        # rather than a copy of the content of the branch.
 
986
        # I did not use a proxy object because that needs much more extensive
 
987
        # testing, and we are only changing one behaviour at the moment.
 
988
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
989
        # then this should be refactored to introduce a tested proxy branch
 
990
        # and a subclass of that for use in overriding clone() and ....
 
991
        # - RBC 20060210
 
992
        result.clone = self._make_reference_clone_function(result)
 
993
        return result
 
994
 
 
995
 
 
996
class Converter5to6(object):
 
997
    """Perform an in-place upgrade of format 5 to format 6"""
 
998
 
 
999
    def convert(self, branch):
 
1000
        # Data for 5 and 6 can peacefully coexist.
 
1001
        format = BzrBranchFormat6()
 
1002
        new_branch = format.open(branch.bzrdir, _found=True)
 
1003
 
 
1004
        # Copy source data into target
 
1005
        new_branch._write_last_revision_info(*branch.last_revision_info())
 
1006
        new_branch.lock_write()
 
1007
        try:
 
1008
            new_branch.set_parent(branch.get_parent())
 
1009
            new_branch.set_bound_location(branch.get_bound_location())
 
1010
            new_branch.set_push_location(branch.get_push_location())
 
1011
        finally:
 
1012
            new_branch.unlock()
 
1013
 
 
1014
        # New branch has no tags by default
 
1015
        new_branch.tags._set_tag_dict({})
 
1016
 
 
1017
        # Copying done; now update target format
 
1018
        new_branch._transport.put_bytes('format',
 
1019
            format.as_string(),
 
1020
            mode=new_branch.bzrdir._get_file_mode())
 
1021
 
 
1022
        # Clean up old files
 
1023
        new_branch._transport.delete('revision-history')
 
1024
        branch.lock_write()
 
1025
        try:
 
1026
            try:
 
1027
                branch.set_parent(None)
 
1028
            except errors.NoSuchFile:
 
1029
                pass
 
1030
            branch.set_bound_location(None)
 
1031
        finally:
 
1032
            branch.unlock()
 
1033
 
 
1034
 
 
1035
class Converter6to7(object):
 
1036
    """Perform an in-place upgrade of format 6 to format 7"""
 
1037
 
 
1038
    def convert(self, branch):
 
1039
        format = BzrBranchFormat7()
 
1040
        branch._set_config_location('stacked_on_location', '')
 
1041
        # update target format
 
1042
        branch._transport.put_bytes('format', format.as_string())
 
1043
 
 
1044
 
 
1045
class Converter7to8(object):
 
1046
    """Perform an in-place upgrade of format 7 to format 8"""
 
1047
 
 
1048
    def convert(self, branch):
 
1049
        format = BzrBranchFormat8()
 
1050
        branch._transport.put_bytes('references', '')
 
1051
        # update target format
 
1052
        branch._transport.put_bytes('format', format.as_string())
 
1053
 
 
1054
 
 
1055