/brz/remove-bazaar

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