/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2020-09-02 16:35:18 UTC
  • mto: (7490.40.109 work)
  • mto: This revision was merged to the branch mainline in revision 7526.
  • Revision ID: jelmer@jelmer.uk-20200902163518-sy9f4unbboljphgu
Handle duplicate directories entries for git.

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