/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2018-05-06 11:48:54 UTC
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@jelmer.uk-20180506114854-h4qd9ojaqy8wxjsd
Move .mailmap to root.

Show diffs side-by-side

added added

removed removed

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