~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Branch now uses BzrDir reasonably sanely.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
23
23
import sys
24
24
from unittest import TestSuite
25
25
from warnings import warn
26
 
import xml.sax.saxutils
27
26
 
28
27
 
29
28
import bzrlib
 
29
import bzrlib.bzrdir as bzrdir
30
30
from bzrlib.config import TreeConfig
31
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
32
from bzrlib.delta import compare_trees
87
87
    # - RBC 20060112
88
88
    base = None
89
89
 
90
 
    _default_initializer = None
91
 
    """The default initializer for making new branches."""
 
90
    @staticmethod
 
91
    def create(base):
 
92
        """Construct the current default format branch in a_bzrdir.
 
93
 
 
94
        This creates the current default BzrDir format, and if that 
 
95
        supports multiple Branch formats, then the default Branch format
 
96
        will take effect.
 
97
        """
 
98
        print "not usable until we have repositories"
 
99
        raise NotImplementedError("not usable right now")
 
100
        return bzrdir.BzrDir.create(base)
92
101
 
93
102
    def __init__(self, *ignored, **ignored_too):
94
103
        raise NotImplementedError('The Branch class is abstract')
100
109
        
101
110
    @staticmethod
102
111
    def open(base, _unsupported=False):
103
 
        """Open an existing branch, rooted at 'base' (url)
104
 
        
105
 
        _unsupported is a private parameter to the Branch class.
 
112
        """Open the repository rooted at base.
 
113
 
 
114
        For instance, if the repository is at URL/.bzr/repository,
 
115
        Repository.open(URL) -> a Repository instance.
106
116
        """
107
 
        t = get_transport(base)
108
 
        mutter("trying to open %r with transport %r", base, t)
109
 
        format = BzrBranchFormat.find_format(t)
110
 
        if not _unsupported and not format.is_supported():
111
 
            # see open_downlevel to open legacy branches.
112
 
            raise errors.UnsupportedFormatError(
113
 
                    'sorry, branch format %s not supported' % format,
114
 
                    ['use a different bzr version',
115
 
                     'or remove the .bzr directory'
116
 
                     ' and "bzr init" again'])
117
 
        return format.open(t)
 
117
        control = bzrdir.BzrDir.open(base, _unsupported)
 
118
        return control.open_branch()
118
119
 
119
120
    @staticmethod
120
121
    def open_containing(url):
128
129
        format, UnknownFormatError or UnsupportedFormatError are raised.
129
130
        If there is one, it is returned, along with the unused portion of url.
130
131
        """
131
 
        t = get_transport(url)
132
 
        # this gets the normalised url back. I.e. '.' -> the full path.
133
 
        url = t.base
134
 
        while True:
135
 
            try:
136
 
                format = BzrBranchFormat.find_format(t)
137
 
                return format.open(t), t.relpath(url)
138
 
            except NotBranchError, e:
139
 
                mutter('not a branch in: %r %s', t.base, e)
140
 
            new_t = t.clone('..')
141
 
            if new_t.base == t.base:
142
 
                # reached the root, whatever that may be
143
 
                raise NotBranchError(path=url)
144
 
            t = new_t
145
 
 
146
 
    @staticmethod
147
 
    def create(base):
148
 
        """Create a new Branch at the url 'bzr'.
149
 
        
150
 
        This will call the current default initializer with base
151
 
        as the only parameter.
152
 
        """
153
 
        return Branch._default_initializer(safe_unicode(base))
 
132
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
133
        return control.open_branch(), relpath
154
134
 
155
135
    @staticmethod
156
136
    @deprecated_function(zero_eight)
161
141
        from bzrlib.workingtree import WorkingTree
162
142
        return WorkingTree.create_standalone(safe_unicode(base)).branch
163
143
 
164
 
    @staticmethod
165
 
    def get_default_initializer():
166
 
        """Return the initializer being used for new branches."""
167
 
        return Branch._default_initializer
168
 
 
169
 
    @staticmethod
170
 
    def set_default_initializer(initializer):
171
 
        """Set the initializer to be used for new branches."""
172
 
        Branch._default_initializer = staticmethod(initializer)
173
 
 
174
144
    def setup_caching(self, cache_root):
175
145
        """Subclasses that care about caching should override this, and set
176
146
        up cached stores located under cache_root.
190
160
 
191
161
    nick = property(_get_nick, _set_nick)
192
162
        
193
 
    def push_stores(self, branch_to):
194
 
        """Copy the content of this branches store to branch_to."""
195
 
        raise NotImplementedError('push_stores is abstract')
196
 
 
197
163
    def lock_write(self):
198
164
        raise NotImplementedError('lock_write is abstract')
199
165
        
257
223
 
258
224
        >>> from bzrlib.workingtree import WorkingTree
259
225
        >>> bzrlib.trace.silent = True
260
 
        >>> br1 = ScratchBranch()
 
226
        >>> d1 = bzrdir.ScratchDir()
 
227
        >>> br1 = d1.open_branch()
261
228
        >>> wt1 = WorkingTree(br1.base, br1)
262
 
        ...
263
 
        >>> br2 = ScratchBranch()
 
229
        >>> d2 = bzrdir.ScratchDir()
 
230
        >>> br2 = d2.open_branch()
264
231
        >>> wt2 = WorkingTree(br2.base, br2)
265
 
        ...
266
232
        >>> br1.missing_revisions(br2)
267
233
        []
268
234
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
427
393
                pass
428
394
        if to_branch_format is None:
429
395
            # use the default
430
 
            br_to = Branch.create(to_location)
 
396
            br_to = bzrdir.BzrDir.create_branch_and_repo(to_location)
431
397
        else:
432
398
            br_to = to_branch_format.initialize(to_location)
433
399
        mutter("copy branch from %s to %s", self, br_to)
434
 
        if basis_branch is not None:
435
 
            basis_branch.push_stores(br_to)
436
400
        if revision is None:
437
401
            revision = self.last_revision()
 
402
        if basis_branch is not None:
 
403
            basis_branch.repository.push_stores(br_to.repository,
 
404
                                                revision=revision)
438
405
        br_to.update_revisions(self, stop_revision=revision)
439
406
        br_to.set_parent(self.base)
440
407
        mutter("copied")
441
408
        return br_to
442
409
 
443
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
444
 
        """ This function returns the file_id(s) involved in the
445
 
            changes between the from_revid revision and the to_revid
446
 
            revision
447
 
        """
448
 
        raise NotImplementedError('fileid_involved_between_revs is abstract')
449
 
 
450
 
    def fileid_involved(self, last_revid=None):
451
 
        """ This function returns the file_id(s) involved in the
452
 
            changes up to the revision last_revid
453
 
            If no parametr is passed, then all file_id[s] present in the
454
 
            repository are returned
455
 
        """
456
 
        raise NotImplementedError('fileid_involved is abstract')
457
 
 
458
 
    def fileid_involved_by_set(self, changes):
459
 
        """ This function returns the file_id(s) involved in the
460
 
            changes present in the set 'changes'
461
 
        """
462
 
        raise NotImplementedError('fileid_involved_by_set is abstract')
463
 
 
464
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
465
 
        """ This function returns the file_id(s) involved in the
466
 
            changes between the from_revid revision and the to_revid
467
 
            revision
468
 
        """
469
 
        raise NotImplementedError('fileid_involved_between_revs is abstract')
470
 
 
471
 
    def fileid_involved(self, last_revid=None):
472
 
        """ This function returns the file_id(s) involved in the
473
 
            changes up to the revision last_revid
474
 
            If no parametr is passed, then all file_id[s] present in the
475
 
            repository are returned
476
 
        """
477
 
        raise NotImplementedError('fileid_involved is abstract')
478
 
 
479
 
    def fileid_involved_by_set(self, changes):
480
 
        """ This function returns the file_id(s) involved in the
481
 
            changes present in the set 'changes'
482
 
        """
483
 
        raise NotImplementedError('fileid_involved_by_set is abstract')
484
 
 
485
 
class BzrBranchFormat(object):
 
410
 
 
411
class BranchFormat(object):
486
412
    """An encapsulation of the initialization and open routines for a format.
487
413
 
488
414
    Formats provide three things:
500
426
    object will be created every time regardless.
501
427
    """
502
428
 
 
429
    _default_format = None
 
430
    """The default format used for new branches."""
 
431
 
503
432
    _formats = {}
504
433
    """The known formats."""
505
434
 
506
435
    @classmethod
507
 
    def find_format(klass, transport):
508
 
        """Return the format registered for URL."""
509
 
        try:
510
 
            format_string = transport.get(".bzr/branch-format").read()
511
 
            return klass._formats[format_string]
512
 
        except NoSuchFile:
513
 
            raise NotBranchError(path=transport.base)
514
 
        except KeyError:
515
 
            raise errors.UnknownFormatError(format_string)
 
436
    def get_default_format(klass):
 
437
        """Return the current default format."""
 
438
        return klass._default_format
516
439
 
517
440
    def get_format_string(self):
518
441
        """Return the ASCII format string that identifies this format."""
541
464
            file_mode = None
542
465
        return dir_mode, file_mode
543
466
 
544
 
    def initialize(self, url):
545
 
        """Create a branch of this format at url and return an open branch."""
546
 
        t = get_transport(url)
547
 
        from bzrlib.weavefile import write_weave_v5
548
 
        from bzrlib.weave import Weave
549
 
        
550
 
        # Create an empty weave
551
 
        sio = StringIO()
552
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
553
 
        empty_weave = sio.getvalue()
554
 
 
555
 
        # Since we don't have a .bzr directory, inherit the
556
 
        # mode from the root directory
557
 
        temp_control = LockableFiles(t, '')
558
 
        temp_control._transport.mkdir('.bzr',
559
 
                                      mode=temp_control._dir_mode)
560
 
        file_mode = temp_control._file_mode
561
 
        del temp_control
562
 
        mutter('created control directory in ' + t.base)
563
 
        control = t.clone('.bzr')
564
 
        dirs = ['revision-store', 'weaves']
565
 
        lock_file = 'branch-lock'
566
 
        utf8_files = [('README', 
567
 
                       "This is a Bazaar-NG control directory.\n"
568
 
                       "Do not change any files in this directory.\n"),
569
 
                      ('branch-format', self.get_format_string()),
570
 
                      ('revision-history', ''),
 
467
    def initialize(self, a_bzrdir):
 
468
        """Create a branch of this format in a_bzrdir."""
 
469
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
470
        utf8_files = [('revision-history', ''),
571
471
                      ('branch-name', ''),
572
472
                      ]
573
 
        files = [('inventory.weave', StringIO(empty_weave)), 
574
 
                 ]
575
473
        
576
 
        # FIXME: RBC 20060125 dont peek under the covers
577
 
        # NB: no need to escape relative paths that are url safe.
578
 
        control.put(lock_file, StringIO(), mode=file_mode)
579
 
        control_files = LockableFiles(control, lock_file)
 
474
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
580
475
        control_files.lock_write()
581
 
        control_files._transport.mkdir_multi(dirs,
582
 
                mode=control_files._dir_mode)
583
476
        try:
584
477
            for file, content in utf8_files:
585
478
                control_files.put_utf8(file, content)
586
 
            for file, content in files:
587
 
                control_files.put(file, content)
588
479
        finally:
589
480
            control_files.unlock()
590
 
        return BzrBranch(t, _format=self, _control_files=control_files)
 
481
        return self.open(a_bzrdir, _found=True)
591
482
 
592
483
    def is_supported(self):
593
484
        """Is this format supported?
598
489
        """
599
490
        return True
600
491
 
601
 
    def open(self, transport):
602
 
        """Fill out the data in branch for the branch at url."""
603
 
        return BzrBranch(transport, _format=self)
 
492
    def open(self, a_bzrdir, _found=False):
 
493
        """Return the branch object for a_bzrdir
 
494
 
 
495
        _found is a private parameter, do not use it. It is used to indicate
 
496
               if format probing has already be done.
 
497
        """
 
498
        if not _found:
 
499
            # we are being called directly and must probe.
 
500
            raise NotImplementedError
 
501
        return BzrBranch(a_bzrdir.transport.clone('..'), _format=self, a_bzrdir=a_bzrdir)
604
502
 
605
503
    @classmethod
606
504
    def register_format(klass, format):
607
505
        klass._formats[format.get_format_string()] = format
608
506
 
609
507
    @classmethod
 
508
    def set_default_format(klass, format):
 
509
        klass._default_format = format
 
510
 
 
511
    @classmethod
610
512
    def unregister_format(klass, format):
611
513
        assert klass._formats[format.get_format_string()] is format
612
514
        del klass._formats[format.get_format_string()]
613
515
 
614
516
 
615
 
class BzrBranchFormat4(BzrBranchFormat):
 
517
class BzrBranchFormat4(BranchFormat):
616
518
    """Bzr branch format 4.
617
519
 
618
520
    This format has:
619
 
     - flat stores
620
 
     - TextStores for texts, inventories,revisions.
621
 
 
622
 
    This format is deprecated: it indexes texts using a text it which is
623
 
    removed in format 5; write support for this format has been removed.
624
 
    """
625
 
 
626
 
    def get_format_string(self):
627
 
        """See BzrBranchFormat.get_format_string()."""
628
 
        return BZR_BRANCH_FORMAT_4
629
 
 
630
 
    def initialize(self, url):
631
 
        """Format 4 branches cannot be created."""
632
 
        raise UninitializableFormat(self)
633
 
 
634
 
    def is_supported(self):
635
 
        """Format 4 is not supported.
636
 
 
637
 
        It is not supported because the model changed from 4 to 5 and the
638
 
        conversion logic is expensive - so doing it on the fly was not 
639
 
        feasible.
640
 
        """
641
 
        return False
642
 
 
643
 
 
644
 
class BzrBranchFormat5(BzrBranchFormat):
645
 
    """Bzr branch format 5.
646
 
 
647
 
    This format has:
648
 
     - weaves for file texts and inventory
649
 
     - flat stores
650
 
     - TextStores for revisions and signatures.
651
 
    """
652
 
 
653
 
    def get_format_string(self):
654
 
        """See BzrBranchFormat.get_format_string()."""
655
 
        return BZR_BRANCH_FORMAT_5
656
 
 
657
 
 
658
 
class BzrBranchFormat6(BzrBranchFormat):
659
 
    """Bzr branch format 6.
660
 
 
661
 
    This format has:
662
 
     - weaves for file texts and inventory
663
 
     - hash subdirectory based stores.
664
 
     - TextStores for revisions and signatures.
665
 
    """
666
 
 
667
 
    def get_format_string(self):
668
 
        """See BzrBranchFormat.get_format_string()."""
669
 
        return BZR_BRANCH_FORMAT_6
670
 
 
671
 
 
672
 
BzrBranchFormat.register_format(BzrBranchFormat4())
673
 
BzrBranchFormat.register_format(BzrBranchFormat5())
674
 
BzrBranchFormat.register_format(BzrBranchFormat6())
675
 
 
676
 
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
677
 
#       make sure that ancestry.weave is deleted (it is never used, but
678
 
#       used to be created)
679
 
 
 
521
     - a revision-history file.
 
522
    """
 
523
 
 
524
    def __init__(self):
 
525
        super(BzrBranchFormat4, self).__init__()
 
526
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
527
 
 
528
# formats which have no format string are not discoverable
 
529
# and not independently creatable, so are not registered.
 
530
# __default_format = BranchFormatXXX()
 
531
# BranchFormat.register_format(__default_format)
 
532
# BranchFormat.set_default_format(__default_format)
 
533
_legacy_formats = [BzrBranchFormat4(),
 
534
                   ]
680
535
 
681
536
class BzrBranch(Branch):
682
537
    """A branch stored in the actual filesystem.
697
552
    # This should match a prefix with a function which accepts
698
553
    REVISION_NAMESPACES = {}
699
554
 
700
 
    def push_stores(self, branch_to):
701
 
        """See Branch.push_stores."""
702
 
        if (not isinstance(self._branch_format, BzrBranchFormat4) or
703
 
            self._branch_format != branch_to._branch_format):
704
 
            from bzrlib.fetch import greedy_fetch
705
 
            mutter("Using fetch logic to push between %s(%s) and %s(%s)",
706
 
                   self, self._branch_format, branch_to, branch_to._branch_format)
707
 
            greedy_fetch(to_branch=branch_to, from_branch=self,
708
 
                         revision=self.last_revision())
709
 
            return
710
 
 
711
 
        # format 4 to format 4 logic only.
712
 
        store_pairs = ((self.text_store,      branch_to.text_store),
713
 
                       (self.inventory_store, branch_to.inventory_store),
714
 
                       (self.revision_store,  branch_to.revision_store))
715
 
        try:
716
 
            for from_store, to_store in store_pairs: 
717
 
                copy_all(from_store, to_store)
718
 
        except UnlistableStore:
719
 
            raise UnlistableBranch(from_store)
720
 
 
721
555
    def __init__(self, transport, init=DEPRECATED_PARAMETER,
722
556
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
723
 
                 _control_files=None):
 
557
                 _control_files=None, a_bzrdir=None):
724
558
        """Create new branch object at a particular location.
725
559
 
726
560
        transport -- A Transport object, defining how to access files.
733
567
            version is not applied.  This is intended only for
734
568
            upgrade/recovery type use; it's not guaranteed that
735
569
            all operations will work on old format branches.
736
 
 
737
 
        In the test suite, creation of new trees is tested using the
738
 
        `ScratchBranch` class.
739
570
        """
740
 
        assert isinstance(transport, Transport), \
741
 
            "%r is not a Transport" % transport
 
571
        self.bzrdir = a_bzrdir
742
572
        self._transport = transport
743
573
        self._base = self._transport.base
 
574
        self._format = _format
744
575
        if _control_files is None:
745
576
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
746
577
                                           'branch-lock')
769
600
                        ['use a different bzr version',
770
601
                         'or remove the .bzr directory'
771
602
                         ' and "bzr init" again'])
772
 
        self.repository = Repository(transport, self._branch_format)
773
 
 
774
 
 
775
 
    @staticmethod
776
 
    def _initialize(base):
777
 
        """Create a bzr branch in the latest format."""
778
 
        return BzrBranchFormat6().initialize(base)
 
603
        self.repository = self.bzrdir.open_repository()
779
604
 
780
605
    def __str__(self):
781
606
        return '%s(%r)' % (self.__class__.__name__, self.base)
1048
873
        history = self._get_truncated_history(revision)
1049
874
        if not bzrlib.osutils.lexists(to_location):
1050
875
            os.mkdir(to_location)
1051
 
        branch_to = BzrBranchFormat6().initialize(to_location)
 
876
        bzrdir_to = self.bzrdir._format.initialize(to_location)
 
877
        self.repository.clone(bzrdir_to)
 
878
        branch_to = bzrdir_to.create_branch()
1052
879
        mutter("copy branch from %s to %s", self, branch_to)
1053
880
 
1054
 
        self.repository.copy(branch_to.repository)
1055
 
        
1056
 
        # must be done *after* history is copied across
1057
881
        # FIXME duplicate code with base .clone().
1058
882
        # .. would template method be useful here?  RBC 20051207
1059
883
        branch_to.set_parent(self.base)
1071
895
        else:
1072
896
            return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1073
897
 
1074
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
1075
 
        """Find file_id(s) which are involved in the changes between revisions.
1076
 
 
1077
 
        This determines the set of revisions which are involved, and then
1078
 
        finds all file ids affected by those revisions.
1079
 
        """
1080
 
        # TODO: jam 20060119 This code assumes that w.inclusions will
1081
 
        #       always be correct. But because of the presence of ghosts
1082
 
        #       it is possible to be wrong.
1083
 
        #       One specific example from Robert Collins:
1084
 
        #       Two branches, with revisions ABC, and AD
1085
 
        #       C is a ghost merge of D.
1086
 
        #       Inclusions doesn't recognize D as an ancestor.
1087
 
        #       If D is ever merged in the future, the weave
1088
 
        #       won't be fixed, because AD never saw revision C
1089
 
        #       to cause a conflict which would force a reweave.
1090
 
        w = self.repository.get_inventory_weave()
1091
 
        from_set = set(w.inclusions([w.lookup(from_revid)]))
1092
 
        to_set = set(w.inclusions([w.lookup(to_revid)]))
1093
 
        included = to_set.difference(from_set)
1094
 
        changed = map(w.idx_to_name, included)
1095
 
        return self._fileid_involved_by_set(changed)
1096
 
 
1097
 
    def fileid_involved(self, last_revid=None):
1098
 
        """Find all file_ids modified in the ancestry of last_revid.
1099
 
 
1100
 
        :param last_revid: If None, last_revision() will be used.
1101
 
        """
1102
 
        w = self.repository.get_inventory_weave()
1103
 
        if not last_revid:
1104
 
            changed = set(w._names)
1105
 
        else:
1106
 
            included = w.inclusions([w.lookup(last_revid)])
1107
 
            changed = map(w.idx_to_name, included)
1108
 
        return self._fileid_involved_by_set(changed)
1109
 
 
1110
 
    def fileid_involved_by_set(self, changes):
1111
 
        """Find all file_ids modified by the set of revisions passed in.
1112
 
 
1113
 
        :param changes: A set() of revision ids
1114
 
        """
1115
 
        # TODO: jam 20060119 This line does *nothing*, remove it.
1116
 
        #       or better yet, change _fileid_involved_by_set so
1117
 
        #       that it takes the inventory weave, rather than
1118
 
        #       pulling it out by itself.
1119
 
        w = self.repository.get_inventory_weave()
1120
 
        return self._fileid_involved_by_set(changes)
1121
 
 
1122
 
    def _fileid_involved_by_set(self, changes):
1123
 
        """Find the set of file-ids affected by the set of revisions.
1124
 
 
1125
 
        :param changes: A set() of revision ids.
1126
 
        :return: A set() of file ids.
1127
 
        
1128
 
        This peaks at the Weave, interpreting each line, looking to
1129
 
        see if it mentions one of the revisions. And if so, includes
1130
 
        the file id mentioned.
1131
 
        This expects both the Weave format, and the serialization
1132
 
        to have a single line per file/directory, and to have
1133
 
        fileid="" and revision="" on that line.
1134
 
        """
1135
 
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
1136
 
                isinstance(self._branch_format, BzrBranchFormat6)), \
1137
 
            "fileid_involved only supported for branches which store inventory as xml"
1138
 
 
1139
 
        w = self.repository.get_inventory_weave()
1140
 
        file_ids = set()
1141
 
        for line in w._weave:
1142
 
 
1143
 
            # it is ugly, but it is due to the weave structure
1144
 
            if not isinstance(line, basestring): continue
1145
 
 
1146
 
            start = line.find('file_id="')+9
1147
 
            if start < 9: continue
1148
 
            end = line.find('"', start)
1149
 
            assert end>= 0
1150
 
            file_id = xml.sax.saxutils.unescape(line[start:end])
1151
 
 
1152
 
            # check if file_id is already present
1153
 
            if file_id in file_ids: continue
1154
 
 
1155
 
            start = line.find('revision="')+10
1156
 
            if start < 10: continue
1157
 
            end = line.find('"', start)
1158
 
            assert end>= 0
1159
 
            revision_id = xml.sax.saxutils.unescape(line[start:end])
1160
 
 
1161
 
            if revision_id in changes:
1162
 
                file_ids.add(file_id)
1163
 
 
1164
 
        return file_ids
1165
 
 
1166
 
 
1167
 
Branch.set_default_initializer(BzrBranch._initialize)
1168
 
 
1169
898
 
1170
899
class BranchTestProviderAdapter(object):
1171
900
    """A tool to generate a suite testing multiple branch formats at once.
1183
912
    
1184
913
    def adapt(self, test):
1185
914
        result = TestSuite()
1186
 
        for format in self._formats:
 
915
        for branch_format, bzrdir_format in self._formats:
1187
916
            new_test = deepcopy(test)
1188
917
            new_test.transport_server = self._transport_server
1189
918
            new_test.transport_readonly_server = self._transport_readonly_server
1190
 
            new_test.branch_format = format
 
919
            new_test.bzrdir_format = bzrdir_format
 
920
            new_test.branch_format = branch_format
1191
921
            def make_new_test_id():
1192
 
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
922
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1193
923
                return lambda: new_id
1194
924
            new_test.id = make_new_test_id()
1195
925
            result.addTest(new_test)
1196
926
        return result
1197
927
 
1198
928
 
1199
 
class ScratchBranch(BzrBranch):
1200
 
    """Special test class: a branch that cleans up after itself.
1201
 
 
1202
 
    >>> b = ScratchBranch()
1203
 
    >>> isdir(b.base)
1204
 
    True
1205
 
    >>> bd = b.base
1206
 
    >>> b._transport.__del__()
1207
 
    >>> isdir(bd)
1208
 
    False
1209
 
    """
1210
 
 
1211
 
    def __init__(self, files=[], dirs=[], transport=None):
1212
 
        """Make a test branch.
1213
 
 
1214
 
        This creates a temporary directory and runs init-tree in it.
1215
 
 
1216
 
        If any files are listed, they are created in the working copy.
1217
 
        """
1218
 
        if transport is None:
1219
 
            transport = bzrlib.transport.local.ScratchTransport()
1220
 
            # local import for scope restriction
1221
 
            from bzrlib.workingtree import WorkingTree
1222
 
            WorkingTree.create_standalone(transport.base)
1223
 
            super(ScratchBranch, self).__init__(transport)
1224
 
        else:
1225
 
            super(ScratchBranch, self).__init__(transport)
1226
 
 
1227
 
        # BzrBranch creates a clone to .bzr and then forgets about the
1228
 
        # original transport. A ScratchTransport() deletes itself and
1229
 
        # everything underneath it when it goes away, so we need to
1230
 
        # grab a local copy to prevent that from happening
1231
 
        self._transport = transport
1232
 
 
1233
 
        for d in dirs:
1234
 
            self._transport.mkdir(d)
1235
 
            
1236
 
        for f in files:
1237
 
            self._transport.put(f, 'content of %s' % f)
1238
 
 
1239
 
    def clone(self):
1240
 
        """
1241
 
        >>> orig = ScratchBranch(files=["file1", "file2"])
1242
 
        >>> os.listdir(orig.base)
1243
 
        [u'.bzr', u'file1', u'file2']
1244
 
        >>> clone = orig.clone()
1245
 
        >>> if os.name != 'nt':
1246
 
        ...   os.path.samefile(orig.base, clone.base)
1247
 
        ... else:
1248
 
        ...   orig.base == clone.base
1249
 
        ...
1250
 
        False
1251
 
        >>> os.listdir(clone.base)
1252
 
        [u'.bzr', u'file1', u'file2']
1253
 
        """
1254
 
        from shutil import copytree
1255
 
        from bzrlib.osutils import mkdtemp
1256
 
        base = mkdtemp()
1257
 
        os.rmdir(base)
1258
 
        copytree(self.base, base, symlinks=True)
1259
 
        return ScratchBranch(
1260
 
            transport=bzrlib.transport.local.ScratchTransport(base))
1261
 
    
1262
 
 
1263
929
######################################################################
1264
930
# predicates
1265
931
 
1266
932
 
1267
 
def is_control_file(filename):
1268
 
    ## FIXME: better check
1269
 
    filename = normpath(filename)
1270
 
    while filename != '':
1271
 
        head, tail = os.path.split(filename)
1272
 
        ## mutter('check %r for control file' % ((head, tail),))
1273
 
        if tail == bzrlib.BZRDIR:
1274
 
            return True
1275
 
        if filename == head:
1276
 
            break
1277
 
        filename = head
1278
 
    return False
 
933
@deprecated_function(zero_eight)
 
934
def ScratchBranch(*args, **kwargs):
 
935
    """See bzrlib.bzrdir.ScratchDir."""
 
936
    d = ScratchDir(*args, **kwargs)
 
937
    return d.open_branch()
 
938
 
 
939
 
 
940
@deprecated_function(zero_eight)
 
941
def is_control_file(*args, **kwargs):
 
942
    """See bzrlib.workingtree.is_control_file."""
 
943
    return bzrlib.workingtree.is_control_file(*args, **kwargs)