/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/branch.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2018-11-16 11:31:40 UTC
  • mfrom: (7143.12.3 annotated-tags)
  • Revision ID: breezy.the.bot@gmail.com-20181116113140-618u04763u0dyxnh
Fix fetching of revisions that are referenced by annotated tags.

Merged from https://code.launchpad.net/~jelmer/brz/annotated-tags/+merge/358536

Show diffs side-by-side

added added

removed removed

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