~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] jam-integration 1527, including branch-formats, help text, misc bug fixes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
from copy import deepcopy
 
19
from cStringIO import StringIO
 
20
import errno
 
21
import os
18
22
import shutil
19
23
import sys
20
 
import os
21
 
import errno
 
24
from unittest import TestSuite
22
25
from warnings import warn
23
 
import xml.sax.saxutils
 
26
try:
 
27
    import xml.sax.saxutils
 
28
except ImportError:
 
29
    raise ImportError("We were unable to import 'xml.sax.saxutils',"
 
30
                      " most likely you have an xml.pyc or xml.pyo file"
 
31
                      " lying around in your bzrlib directory."
 
32
                      " Please remove it.")
24
33
from cStringIO import StringIO
25
34
 
26
35
 
27
36
import bzrlib
28
 
from bzrlib.trace import mutter, note
29
 
from bzrlib.osutils import (isdir, quotefn,
30
 
                            rename, splitpath, sha_file,
31
 
                            file_kind, abspath, normpath, pathjoin)
 
37
from bzrlib.config import TreeConfig
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
39
from bzrlib.delta import compare_trees
32
40
import bzrlib.errors as errors
33
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
42
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
 
                           DivergedBranches, LockError, UnlistableStore,
 
43
                           DivergedBranches, LockError,
 
44
                           UninitializableFormat,
 
45
                           UnlistableStore,
36
46
                           UnlistableBranch, NoSuchFile, NotVersionedError,
37
47
                           NoWorkingTree)
38
 
from bzrlib.textui import show_status
39
 
from bzrlib.config import TreeConfig
40
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
41
 
from bzrlib.delta import compare_trees
42
48
import bzrlib.inventory as inventory
43
49
from bzrlib.inventory import Inventory
44
50
from bzrlib.lockable_files import LockableFiles
 
51
from bzrlib.osutils import (isdir, quotefn,
 
52
                            rename, splitpath, sha_file,
 
53
                            file_kind, abspath, normpath, pathjoin,
 
54
                            safe_unicode,
 
55
                            )
 
56
from bzrlib.textui import show_status
 
57
from bzrlib.trace import mutter, note
 
58
from bzrlib.tree import EmptyTree, RevisionTree
 
59
from bzrlib.repository import Repository
45
60
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
46
 
from bzrlib.repository import Repository
47
61
from bzrlib.store import copy_all
 
62
from bzrlib.symbol_versioning import *
48
63
import bzrlib.transactions as transactions
49
64
from bzrlib.transport import Transport, get_transport
50
65
from bzrlib.tree import EmptyTree, RevisionTree
75
90
    base
76
91
        Base directory/url of the branch.
77
92
    """
 
93
    # this is really an instance variable - FIXME move it there
 
94
    # - RBC 20060112
78
95
    base = None
79
96
 
 
97
    _default_initializer = None
 
98
    """The default initializer for making new branches."""
 
99
 
80
100
    def __init__(self, *ignored, **ignored_too):
81
101
        raise NotImplementedError('The Branch class is abstract')
82
102
 
83
103
    @staticmethod
84
104
    def open_downlevel(base):
85
 
        """Open a branch which may be of an old format.
86
 
        
87
 
        Only local branches are supported."""
88
 
        return BzrBranch(get_transport(base), relax_version_check=True)
 
105
        """Open a branch which may be of an old format."""
 
106
        return Branch.open(base, _unsupported=True)
89
107
        
90
108
    @staticmethod
91
 
    def open(base):
92
 
        """Open an existing branch, rooted at 'base' (url)"""
 
109
    def open(base, _unsupported=False):
 
110
        """Open an existing branch, rooted at 'base' (url)
 
111
        
 
112
        _unsupported is a private parameter to the Branch class.
 
113
        """
93
114
        t = get_transport(base)
94
115
        mutter("trying to open %r with transport %r", base, t)
95
 
        return BzrBranch(t)
 
116
        format = BzrBranchFormat.find_format(t)
 
117
        if not _unsupported and not format.is_supported():
 
118
            # see open_downlevel to open legacy branches.
 
119
            raise errors.UnsupportedFormatError(
 
120
                    'sorry, branch format %s not supported' % format,
 
121
                    ['use a different bzr version',
 
122
                     'or remove the .bzr directory'
 
123
                     ' and "bzr init" again'])
 
124
        return format.open(t)
96
125
 
97
126
    @staticmethod
98
127
    def open_containing(url):
102
131
 
103
132
        Basically we keep looking up until we find the control directory or
104
133
        run into the root.  If there isn't one, raises NotBranchError.
 
134
        If there is one and it is either an unrecognised format or an unsupported 
 
135
        format, UnknownFormatError or UnsupportedFormatError are raised.
105
136
        If there is one, it is returned, along with the unused portion of url.
106
137
        """
107
138
        t = get_transport(url)
 
139
        # this gets the normalised url back. I.e. '.' -> the full path.
 
140
        url = t.base
108
141
        while True:
109
142
            try:
110
 
                return BzrBranch(t), t.relpath(url)
 
143
                format = BzrBranchFormat.find_format(t)
 
144
                return format.open(t), t.relpath(url)
111
145
            except NotBranchError, e:
112
146
                mutter('not a branch in: %r %s', t.base, e)
113
147
            new_t = t.clone('..')
117
151
            t = new_t
118
152
 
119
153
    @staticmethod
 
154
    def create(base):
 
155
        """Create a new Branch at the url 'bzr'.
 
156
        
 
157
        This will call the current default initializer with base
 
158
        as the only parameter.
 
159
        """
 
160
        return Branch._default_initializer(safe_unicode(base))
 
161
 
 
162
    @staticmethod
 
163
    @deprecated_function(zero_eight)
120
164
    def initialize(base):
121
 
        """Create a new branch, rooted at 'base' (url)"""
122
 
        t = get_transport(unicode(base))
123
 
        return BzrBranch(t, init=True)
 
165
        """Create a new working tree and branch, rooted at 'base' (url)
 
166
 
 
167
        NOTE: This will soon be deprecated in favour of creation
 
168
        through a BzrDir.
 
169
        """
 
170
        # imported here to prevent scope creep as this is going.
 
171
        from bzrlib.workingtree import WorkingTree
 
172
        return WorkingTree.create_standalone(safe_unicode(base)).branch
 
173
 
 
174
    @staticmethod
 
175
    def get_default_initializer():
 
176
        """Return the initializer being used for new branches."""
 
177
        return Branch._default_initializer
 
178
 
 
179
    @staticmethod
 
180
    def set_default_initializer(initializer):
 
181
        """Set the initializer to be used for new branches."""
 
182
        Branch._default_initializer = staticmethod(initializer)
124
183
 
125
184
    def setup_caching(self, cache_root):
126
185
        """Subclasses that care about caching should override this, and set
206
265
        If self and other have not diverged, return a list of the revisions
207
266
        present in other, but missing from self.
208
267
 
209
 
        >>> from bzrlib.commit import commit
210
268
        >>> bzrlib.trace.silent = True
211
269
        >>> br1 = ScratchBranch()
212
270
        >>> br2 = ScratchBranch()
213
271
        >>> br1.missing_revisions(br2)
214
272
        []
215
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
273
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
216
274
        >>> br1.missing_revisions(br2)
217
275
        [u'REVISION-ID-1']
218
276
        >>> br2.missing_revisions(br1)
219
277
        []
220
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
278
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
221
279
        >>> br1.missing_revisions(br2)
222
280
        []
223
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
281
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
224
282
        >>> br1.missing_revisions(br2)
225
283
        [u'REVISION-ID-2A']
226
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
284
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
227
285
        >>> br1.missing_revisions(br2)
228
286
        Traceback (most recent call last):
229
287
        DivergedBranches: These branches have diverged.  Try merge.
345
403
        if revno < 1 or revno > self.revno():
346
404
            raise InvalidRevisionNumber(revno)
347
405
        
348
 
    def sign_revision(self, revision_id, gpg_strategy):
349
 
        raise NotImplementedError('sign_revision is abstract')
350
 
 
351
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
352
 
        raise NotImplementedError('store_revision_signature is abstract')
353
 
 
354
406
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
355
407
        """Copy this branch into the existing directory to_location.
356
408
 
372
424
        to_branch_type
373
425
            Branch type of destination branch
374
426
        """
375
 
        # circular import protection
376
 
        from bzrlib.merge import build_working_dir
377
 
 
 
427
        from bzrlib.workingtree import WorkingTree
378
428
        assert isinstance(to_location, basestring)
379
429
        if not bzrlib.osutils.lexists(to_location):
380
430
            os.mkdir(to_location)
381
431
        if to_branch_type is None:
382
432
            to_branch_type = BzrBranch
 
433
        print "FIXME use a branch format here"
383
434
        br_to = to_branch_type.initialize(to_location)
384
435
        mutter("copy branch from %s to %s", self, br_to)
385
436
        if basis_branch is not None:
386
437
            basis_branch.push_stores(br_to)
387
 
        br_to.working_tree().set_root_id(self.get_root_id())
388
438
        if revision is None:
389
439
            revision = self.last_revision()
390
440
        br_to.update_revisions(self, stop_revision=revision)
391
441
        br_to.set_parent(self.base)
392
 
        build_working_dir(to_location)
 
442
        WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
393
443
        mutter("copied")
394
444
        return br_to
395
445
 
435
485
        """
436
486
        raise NotImplementedError('fileid_involved_by_set is abstract')
437
487
 
 
488
class BzrBranchFormat(object):
 
489
    """An encapsulation of the initialization and open routines for a format.
 
490
 
 
491
    Formats provide three things:
 
492
     * An initialization routine,
 
493
     * a format string,
 
494
     * an open routine.
 
495
 
 
496
    Formats are placed in an dict by their format string for reference 
 
497
    during branch opening. Its not required that these be instances, they
 
498
    can be classes themselves with class methods - it simply depends on 
 
499
    whether state is needed for a given format or not.
 
500
 
 
501
    Once a format is deprecated, just deprecate the initialize and open
 
502
    methods on the format class. Do not deprecate the object, as the 
 
503
    object will be created every time regardless.
 
504
    """
 
505
 
 
506
    _formats = {}
 
507
    """The known formats."""
 
508
 
 
509
    @classmethod
 
510
    def find_format(klass, transport):
 
511
        """Return the format registered for URL."""
 
512
        try:
 
513
            format_string = transport.get(".bzr/branch-format").read()
 
514
            return klass._formats[format_string]
 
515
        except NoSuchFile:
 
516
            raise NotBranchError(path=transport.base)
 
517
        except KeyError:
 
518
            raise errors.UnknownFormatError(format_string)
 
519
 
 
520
    def get_format_string(self):
 
521
        """Return the ASCII format string that identifies this format."""
 
522
        raise NotImplementedError(self.get_format_string)
 
523
 
 
524
    def _find_modes(self, t):
 
525
        """Determine the appropriate modes for files and directories.
 
526
        
 
527
        FIXME: When this merges into, or from storage,
 
528
        this code becomes delgatable to a LockableFiles instance.
 
529
 
 
530
        For now its cribbed and returns (dir_mode, file_mode)
 
531
        """
 
532
        try:
 
533
            st = t.stat('.')
 
534
        except errors.TransportNotPossible:
 
535
            dir_mode = 0755
 
536
            file_mode = 0644
 
537
        else:
 
538
            dir_mode = st.st_mode & 07777
 
539
            # Remove the sticky and execute bits for files
 
540
            file_mode = dir_mode & ~07111
 
541
        if not BzrBranch._set_dir_mode:
 
542
            dir_mode = None
 
543
        if not BzrBranch._set_file_mode:
 
544
            file_mode = None
 
545
        return dir_mode, file_mode
 
546
 
 
547
    def initialize(self, url):
 
548
        """Create a branch of this format at url and return an open branch."""
 
549
        t = get_transport(url)
 
550
        from bzrlib.weavefile import write_weave_v5
 
551
        from bzrlib.weave import Weave
 
552
        
 
553
        # Create an empty weave
 
554
        sio = StringIO()
 
555
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
556
        empty_weave = sio.getvalue()
 
557
 
 
558
        # Since we don't have a .bzr directory, inherit the
 
559
        # mode from the root directory
 
560
        temp_control = LockableFiles(t, '')
 
561
        temp_control._transport.mkdir('.bzr',
 
562
                                      mode=temp_control._dir_mode)
 
563
        file_mode = temp_control._file_mode
 
564
        del temp_control
 
565
        mutter('created control directory in ' + t.base)
 
566
        control = t.clone('.bzr')
 
567
        dirs = ['revision-store', 'weaves']
 
568
        lock_file = 'branch-lock'
 
569
        utf8_files = [('README', 
 
570
                       "This is a Bazaar-NG control directory.\n"
 
571
                       "Do not change any files in this directory.\n"),
 
572
                      ('branch-format', self.get_format_string()),
 
573
                      ('revision-history', ''),
 
574
                      ('branch-name', ''),
 
575
                      ]
 
576
        files = [('inventory.weave', StringIO(empty_weave)), 
 
577
                 ]
 
578
        
 
579
        # FIXME: RBC 20060125 dont peek under the covers
 
580
        # NB: no need to escape relative paths that are url safe.
 
581
        control.put(lock_file, StringIO(), mode=file_mode)
 
582
        control_files = LockableFiles(control, lock_file)
 
583
        control_files.lock_write()
 
584
        control_files._transport.mkdir_multi(dirs,
 
585
                mode=control_files._dir_mode)
 
586
        try:
 
587
            for file, content in utf8_files:
 
588
                control_files.put_utf8(file, content)
 
589
            for file, content in files:
 
590
                control_files.put(file, content)
 
591
        finally:
 
592
            control_files.unlock()
 
593
        return BzrBranch(t, _format=self, _control_files=control_files)
 
594
 
 
595
    def is_supported(self):
 
596
        """Is this format supported?
 
597
 
 
598
        Supported formats can be initialized and opened.
 
599
        Unsupported formats may not support initialization or committing or 
 
600
        some other features depending on the reason for not being supported.
 
601
        """
 
602
        return True
 
603
 
 
604
    def open(self, transport):
 
605
        """Fill out the data in branch for the branch at url."""
 
606
        return BzrBranch(transport, _format=self)
 
607
 
 
608
    @classmethod
 
609
    def register_format(klass, format):
 
610
        klass._formats[format.get_format_string()] = format
 
611
 
 
612
    @classmethod
 
613
    def unregister_format(klass, format):
 
614
        assert klass._formats[format.get_format_string()] is format
 
615
        del klass._formats[format.get_format_string()]
 
616
 
 
617
 
 
618
class BzrBranchFormat4(BzrBranchFormat):
 
619
    """Bzr branch format 4.
 
620
 
 
621
    This format has:
 
622
     - flat stores
 
623
     - TextStores for texts, inventories,revisions.
 
624
 
 
625
    This format is deprecated: it indexes texts using a text it which is
 
626
    removed in format 5; write support for this format has been removed.
 
627
    """
 
628
 
 
629
    def get_format_string(self):
 
630
        """See BzrBranchFormat.get_format_string()."""
 
631
        return BZR_BRANCH_FORMAT_4
 
632
 
 
633
    def initialize(self, url):
 
634
        """Format 4 branches cannot be created."""
 
635
        raise UninitializableFormat(self)
 
636
 
 
637
    def is_supported(self):
 
638
        """Format 4 is not supported.
 
639
 
 
640
        It is not supported because the model changed from 4 to 5 and the
 
641
        conversion logic is expensive - so doing it on the fly was not 
 
642
        feasible.
 
643
        """
 
644
        return False
 
645
 
 
646
 
 
647
class BzrBranchFormat5(BzrBranchFormat):
 
648
    """Bzr branch format 5.
 
649
 
 
650
    This format has:
 
651
     - weaves for file texts and inventory
 
652
     - flat stores
 
653
     - TextStores for revisions and signatures.
 
654
    """
 
655
 
 
656
    def get_format_string(self):
 
657
        """See BzrBranchFormat.get_format_string()."""
 
658
        return BZR_BRANCH_FORMAT_5
 
659
 
 
660
 
 
661
class BzrBranchFormat6(BzrBranchFormat):
 
662
    """Bzr branch format 6.
 
663
 
 
664
    This format has:
 
665
     - weaves for file texts and inventory
 
666
     - hash subdirectory based stores.
 
667
     - TextStores for revisions and signatures.
 
668
    """
 
669
 
 
670
    def get_format_string(self):
 
671
        """See BzrBranchFormat.get_format_string()."""
 
672
        return BZR_BRANCH_FORMAT_6
 
673
 
 
674
 
 
675
BzrBranchFormat.register_format(BzrBranchFormat4())
 
676
BzrBranchFormat.register_format(BzrBranchFormat5())
 
677
BzrBranchFormat.register_format(BzrBranchFormat6())
 
678
 
 
679
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
 
680
#       make sure that ancestry.weave is deleted (it is never used, but
 
681
#       used to be created)
 
682
 
438
683
 
439
684
class BzrBranch(Branch):
440
685
    """A branch stored in the actual filesystem.
457
702
 
458
703
    def push_stores(self, branch_to):
459
704
        """See Branch.push_stores."""
460
 
        if (self._branch_format != branch_to._branch_format
461
 
            or self._branch_format != 4):
 
705
        if (not isinstance(self._branch_format, BzrBranchFormat4) or
 
706
            self._branch_format != branch_to._branch_format):
462
707
            from bzrlib.fetch import greedy_fetch
463
 
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
708
            mutter("Using fetch logic to push between %s(%s) and %s(%s)",
464
709
                   self, self._branch_format, branch_to, branch_to._branch_format)
465
710
            greedy_fetch(to_branch=branch_to, from_branch=self,
466
711
                         revision=self.last_revision())
467
712
            return
468
713
 
 
714
        # format 4 to format 4 logic only.
469
715
        store_pairs = ((self.text_store,      branch_to.text_store),
470
716
                       (self.inventory_store, branch_to.inventory_store),
471
717
                       (self.revision_store,  branch_to.revision_store))
475
721
        except UnlistableStore:
476
722
            raise UnlistableBranch(from_store)
477
723
 
478
 
    def __init__(self, transport, init=False,
479
 
                 relax_version_check=False):
 
724
    def __init__(self, transport, init=DEPRECATED_PARAMETER,
 
725
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
726
                 _control_files=None):
480
727
        """Create new branch object at a particular location.
481
728
 
482
729
        transport -- A Transport object, defining how to access files.
495
742
        """
496
743
        assert isinstance(transport, Transport), \
497
744
            "%r is not a Transport" % transport
498
 
        # TODO: jam 20060103 We create a clone of this transport at .bzr/
499
 
        #       and then we forget about it, should we keep a handle to it?
500
 
        self._base = transport.base
501
 
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR),
 
745
        self._transport = transport
 
746
        self._base = self._transport.base
 
747
        if _control_files is None:
 
748
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
502
749
                                           'branch-lock')
503
 
        if init:
504
 
            self._make_control()
505
 
        self._check_format(relax_version_check)
 
750
        self.control_files = _control_files
 
751
        if deprecated_passed(init):
 
752
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
753
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
754
                 DeprecationWarning,
 
755
                 stacklevel=2)
 
756
            if init:
 
757
                # this is slower than before deprecation, oh well never mind.
 
758
                # -> its deprecated.
 
759
                self._initialize(transport.base)
 
760
        self._check_format(_format)
 
761
        if deprecated_passed(relax_version_check):
 
762
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
763
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
764
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
765
                 "open() method.",
 
766
                 DeprecationWarning,
 
767
                 stacklevel=2)
 
768
            if (not relax_version_check
 
769
                and not self._branch_format.is_supported()):
 
770
                raise errors.UnsupportedFormatError(
 
771
                        'sorry, branch format %r not supported' % fmt,
 
772
                        ['use a different bzr version',
 
773
                         'or remove the .bzr directory'
 
774
                         ' and "bzr init" again'])
506
775
        self.repository = Repository(transport, self._branch_format)
507
776
 
 
777
 
 
778
    @staticmethod
 
779
    def _initialize(base):
 
780
        """Create a bzr branch in the latest format."""
 
781
        return BzrBranchFormat6().initialize(base)
 
782
 
508
783
    def __str__(self):
509
784
        return '%s(%r)' % (self.__class__.__name__, self.base)
510
785
 
557
832
        """See Branch.abspath."""
558
833
        return self.control_files._transport.abspath(name)
559
834
 
560
 
    def _make_control(self):
561
 
        from bzrlib.inventory import Inventory
562
 
        from bzrlib.weavefile import write_weave_v5
563
 
        from bzrlib.weave import Weave
564
 
        
565
 
        # Create an empty inventory
566
 
        sio = StringIO()
567
 
        # if we want per-tree root ids then this is the place to set
568
 
        # them; they're not needed for now and so ommitted for
569
 
        # simplicity.
570
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
571
 
        empty_inv = sio.getvalue()
572
 
        sio = StringIO()
573
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
574
 
        empty_weave = sio.getvalue()
575
 
 
576
 
        dirs = ['', 'revision-store', 'weaves']
577
 
        files = [('README', 
578
 
            "This is a Bazaar-NG control directory.\n"
579
 
            "Do not change any files in this directory.\n"),
580
 
            ('branch-format', BZR_BRANCH_FORMAT_6),
581
 
            ('revision-history', ''),
582
 
            ('branch-name', ''),
583
 
            ('branch-lock', ''),
584
 
            ('pending-merges', ''),
585
 
            ('inventory', empty_inv),
586
 
            ('inventory.weave', empty_weave),
587
 
        ]
588
 
        cfe = self.control_files._escape
589
 
        # FIXME: RBC 20060125 dont peek under the covers
590
 
        self.control_files._transport.mkdir_multi([cfe(d) for d in dirs],
591
 
                mode=self.control_files._dir_mode)
592
 
        self.control_files.lock_write()
593
 
        try:
594
 
            for file, content in files:
595
 
                self.control_files.put_utf8(file, content)
596
 
            mutter('created control directory in ' + self.base)
597
 
        finally:
598
 
            self.control_files.unlock()
599
 
 
600
 
    def _check_format(self, relax_version_check):
601
 
        """Check this branch format is supported.
602
 
 
603
 
        The format level is stored, as an integer, in
 
835
    def _check_format(self, format):
 
836
        """Identify the branch format if needed.
 
837
 
 
838
        The format is stored as a reference to the format object in
604
839
        self._branch_format for code that needs to check it later.
605
840
 
606
 
        In the future, we might need different in-memory Branch
607
 
        classes to support downlevel branches.  But not yet.
 
841
        The format parameter is either None or the branch format class
 
842
        used to open this branch.
608
843
        """
609
 
        try:
610
 
            fmt = self.control_files.get_utf8('branch-format').read()
611
 
        except NoSuchFile:
612
 
            raise NotBranchError(path=self.base)
613
 
        mutter("got branch format %r", fmt)
614
 
        if fmt == BZR_BRANCH_FORMAT_6:
615
 
            self._branch_format = 6
616
 
        elif fmt == BZR_BRANCH_FORMAT_5:
617
 
            self._branch_format = 5
618
 
        elif fmt == BZR_BRANCH_FORMAT_4:
619
 
            self._branch_format = 4
620
 
 
621
 
        if (not relax_version_check
622
 
            and self._branch_format not in (5, 6)):
623
 
            raise errors.UnsupportedFormatError(
624
 
                           'sorry, branch format %r not supported' % fmt,
625
 
                           ['use a different bzr version',
626
 
                            'or remove the .bzr directory'
627
 
                            ' and "bzr init" again'])
 
844
        if format is None:
 
845
            format = BzrBranchFormat.find_format(self._transport)
 
846
        self._branch_format = format
 
847
        mutter("got branch format %s", self._branch_format)
628
848
 
629
849
    @needs_read_lock
630
850
    def get_root_id(self):
631
851
        """See Branch.get_root_id."""
632
 
        inv = self.repository.get_inventory(self.last_revision())
633
 
        return inv.root.file_id
 
852
        tree = self.repository.revision_tree(self.last_revision())
 
853
        return tree.inventory.root.file_id
634
854
 
635
855
    def lock_write(self):
636
856
        # TODO: test for failed two phase locks. This is known broken.
670
890
    @needs_write_lock
671
891
    def set_revision_history(self, rev_history):
672
892
        """See Branch.set_revision_history."""
673
 
        old_revision = self.last_revision()
674
 
        new_revision = rev_history[-1]
675
893
        self.control_files.put_utf8(
676
894
            'revision-history', '\n'.join(rev_history))
677
 
        try:
678
 
            # FIXME: RBC 20051207 this smells wrong, last_revision in the 
679
 
            # working tree may be != to last_revision in the branch - so
680
 
            # why is this passing in the branches last_revision ?
681
 
            self.working_tree().set_last_revision(new_revision, old_revision)
682
 
        except NoWorkingTree:
683
 
            mutter('Unable to set_last_revision without a working tree.')
684
895
 
685
896
    def get_revision_delta(self, revno):
686
897
        """Return the delta for one revision.
769
980
    def working_tree(self):
770
981
        """See Branch.working_tree."""
771
982
        from bzrlib.workingtree import WorkingTree
772
 
        if self.base.find('://') != -1:
 
983
        from bzrlib.transport.local import LocalTransport
 
984
        if (self.base.find('://') != -1 or 
 
985
            not isinstance(self._transport, LocalTransport)):
773
986
            raise NoWorkingTree(self.base)
774
987
        return WorkingTree(self.base, branch=self)
775
988
 
838
1051
 
839
1052
    @needs_read_lock
840
1053
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
 
1054
        # prevent leakage
 
1055
        from bzrlib.workingtree import WorkingTree
841
1056
        assert isinstance(to_location, basestring)
842
1057
        if basis_branch is not None:
843
1058
            note("basis_branch is not supported for fast weave copy yet.")
847
1062
            os.mkdir(to_location)
848
1063
        branch_to = Branch.initialize(to_location)
849
1064
        mutter("copy branch from %s to %s", self, branch_to)
850
 
        branch_to.working_tree().set_root_id(self.get_root_id())
851
1065
 
852
1066
        self.repository.copy(branch_to.repository)
853
1067
        
854
1068
        # must be done *after* history is copied across
855
1069
        # FIXME duplicate code with base .clone().
856
 
        # .. would template method be useful here.  RBC 20051207
 
1070
        # .. would template method be useful here?  RBC 20051207
857
1071
        branch_to.set_parent(self.base)
858
1072
        branch_to.append_revision(*history)
859
 
        # circular import protection
860
 
        from bzrlib.merge import build_working_dir
861
 
        build_working_dir(to_location)
 
1073
        # FIXME: this should be in workingtree.clone
 
1074
        WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
862
1075
        mutter("copied")
863
1076
        return branch_to
864
1077
 
865
1078
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
1079
        print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
866
1080
        if to_branch_type is None:
867
1081
            to_branch_type = BzrBranch
868
1082
 
934
1148
        to have a single line per file/directory, and to have
935
1149
        fileid="" and revision="" on that line.
936
1150
        """
937
 
        assert self._branch_format in (5, 6), \
 
1151
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
 
1152
                isinstance(self._branch_format, BzrBranchFormat6)), \
938
1153
            "fileid_involved only supported for branches which store inventory as xml"
939
1154
 
940
1155
        w = self.repository.get_inventory_weave()
965
1180
        return file_ids
966
1181
 
967
1182
 
 
1183
Branch.set_default_initializer(BzrBranch._initialize)
 
1184
 
 
1185
 
 
1186
class BranchTestProviderAdapter(object):
 
1187
    """A tool to generate a suite testing multiple branch formats at once.
 
1188
 
 
1189
    This is done by copying the test once for each transport and injecting
 
1190
    the transport_server, transport_readonly_server, and branch_format
 
1191
    classes into each copy. Each copy is also given a new id() to make it
 
1192
    easy to identify.
 
1193
    """
 
1194
 
 
1195
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1196
        self._transport_server = transport_server
 
1197
        self._transport_readonly_server = transport_readonly_server
 
1198
        self._formats = formats
 
1199
    
 
1200
    def adapt(self, test):
 
1201
        result = TestSuite()
 
1202
        for format in self._formats:
 
1203
            new_test = deepcopy(test)
 
1204
            new_test.transport_server = self._transport_server
 
1205
            new_test.transport_readonly_server = self._transport_readonly_server
 
1206
            new_test.branch_format = format
 
1207
            def make_new_test_id():
 
1208
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1209
                return lambda: new_id
 
1210
            new_test.id = make_new_test_id()
 
1211
            result.addTest(new_test)
 
1212
        return result
 
1213
 
 
1214
 
968
1215
class ScratchBranch(BzrBranch):
969
1216
    """Special test class: a branch that cleans up after itself.
970
1217
 
986
1233
        """
987
1234
        if transport is None:
988
1235
            transport = bzrlib.transport.local.ScratchTransport()
989
 
            super(ScratchBranch, self).__init__(transport, init=True)
 
1236
            # local import for scope restriction
 
1237
            from bzrlib.workingtree import WorkingTree
 
1238
            WorkingTree.create_standalone(transport.base)
 
1239
            super(ScratchBranch, self).__init__(transport)
990
1240
        else:
991
1241
            super(ScratchBranch, self).__init__(transport)
992
1242