~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2006-02-22 04:29:54 UTC
  • mfrom: (1566 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1569.
  • Revision ID: mbp@sourcefrog.net-20060222042954-60333f08dd56a646
[merge] from bzr.dev before integration
Fix undefined ordering in sign_my_revisions breaking tests

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
30
30
                      " most likely you have an xml.pyc or xml.pyo file"
31
31
                      " lying around in your bzrlib directory."
32
32
                      " Please remove it.")
33
 
from cStringIO import StringIO
34
33
 
35
34
 
36
35
import bzrlib
 
36
import bzrlib.bzrdir as bzrdir
37
37
from bzrlib.config import TreeConfig
38
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
39
from bzrlib.delta import compare_trees
57
57
from bzrlib.trace import mutter, note
58
58
from bzrlib.tree import EmptyTree, RevisionTree
59
59
from bzrlib.repository import Repository
60
 
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
 
60
from bzrlib.revision import (
 
61
                             get_intervening_revisions,
 
62
                             is_ancestor,
 
63
                             NULL_REVISION,
 
64
                             Revision,
 
65
                             )
61
66
from bzrlib.store import copy_all
62
67
from bzrlib.symbol_versioning import *
63
68
import bzrlib.transactions as transactions
94
99
    # - RBC 20060112
95
100
    base = None
96
101
 
97
 
    _default_initializer = None
98
 
    """The default initializer for making new branches."""
99
 
 
100
102
    def __init__(self, *ignored, **ignored_too):
101
103
        raise NotImplementedError('The Branch class is abstract')
102
104
 
103
105
    @staticmethod
 
106
    @deprecated_method(zero_eight)
104
107
    def open_downlevel(base):
105
108
        """Open a branch which may be of an old format."""
106
109
        return Branch.open(base, _unsupported=True)
107
110
        
108
111
    @staticmethod
109
112
    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
        """Open the repository rooted at base.
 
114
 
 
115
        For instance, if the repository is at URL/.bzr/repository,
 
116
        Repository.open(URL) -> a Repository instance.
113
117
        """
114
 
        t = get_transport(base)
115
 
        mutter("trying to open %r with transport %r", base, 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)
 
118
        control = bzrdir.BzrDir.open(base, _unsupported)
 
119
        return control.open_branch(_unsupported)
125
120
 
126
121
    @staticmethod
127
122
    def open_containing(url):
135
130
        format, UnknownFormatError or UnsupportedFormatError are raised.
136
131
        If there is one, it is returned, along with the unused portion of url.
137
132
        """
138
 
        t = get_transport(url)
139
 
        # this gets the normalised url back. I.e. '.' -> the full path.
140
 
        url = t.base
141
 
        while True:
142
 
            try:
143
 
                format = BzrBranchFormat.find_format(t)
144
 
                return format.open(t), t.relpath(url)
145
 
            except NotBranchError, e:
146
 
                mutter('not a branch in: %r %s', t.base, e)
147
 
            new_t = t.clone('..')
148
 
            if new_t.base == t.base:
149
 
                # reached the root, whatever that may be
150
 
                raise NotBranchError(path=url)
151
 
            t = new_t
152
 
 
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))
 
133
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
134
        return control.open_branch(), relpath
161
135
 
162
136
    @staticmethod
163
137
    @deprecated_function(zero_eight)
167
141
        NOTE: This will soon be deprecated in favour of creation
168
142
        through a BzrDir.
169
143
        """
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)
 
144
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
183
145
 
184
146
    def setup_caching(self, cache_root):
185
147
        """Subclasses that care about caching should override this, and set
200
162
 
201
163
    nick = property(_get_nick, _set_nick)
202
164
        
203
 
    def push_stores(self, branch_to):
204
 
        """Copy the content of this branches store to branch_to."""
205
 
        raise NotImplementedError('push_stores is abstract')
206
 
 
207
165
    def lock_write(self):
208
166
        raise NotImplementedError('lock_write is abstract')
209
167
        
225
183
        """
226
184
        raise NotImplementedError('abspath is abstract')
227
185
 
 
186
    @needs_write_lock
 
187
    def fetch(self, from_branch, last_revision=None, pb=None):
 
188
        """Copy revisions from from_branch into this branch.
 
189
 
 
190
        :param from_branch: Where to copy from.
 
191
        :param last_revision: What revision to stop at (None for at the end
 
192
                              of the branch.
 
193
        :param pb: An optional progress bar to use.
 
194
 
 
195
        Returns the copied revision count and the failed revisions in a tuple:
 
196
        (copied, failures).
 
197
        """
 
198
        if self.base == from_branch.base:
 
199
            raise Exception("can't fetch from a branch to itself %s, %s" % 
 
200
                            (self.base, to_branch.base))
 
201
        if pb is None:
 
202
            pb = bzrlib.ui.ui_factory.progress_bar()
 
203
 
 
204
        from_branch.lock_read()
 
205
        try:
 
206
            if last_revision is None:
 
207
                pb.update('get source history')
 
208
                from_history = from_branch.revision_history()
 
209
                if from_history:
 
210
                    last_revision = from_history[-1]
 
211
                else:
 
212
                    # no history in the source branch
 
213
                    last_revision = NULL_REVISION
 
214
            return self.repository.fetch(from_branch.repository,
 
215
                                         revision_id=last_revision,
 
216
                                         pb=pb)
 
217
        finally:
 
218
            from_branch.unlock()
 
219
 
228
220
    def get_root_id(self):
229
221
        """Return the id of this branches root"""
230
222
        raise NotImplementedError('get_root_id is abstract')
265
257
        If self and other have not diverged, return a list of the revisions
266
258
        present in other, but missing from self.
267
259
 
 
260
        >>> from bzrlib.workingtree import WorkingTree
268
261
        >>> bzrlib.trace.silent = True
269
 
        >>> br1 = ScratchBranch()
270
 
        >>> br2 = ScratchBranch()
 
262
        >>> d1 = bzrdir.ScratchDir()
 
263
        >>> br1 = d1.open_branch()
 
264
        >>> wt1 = d1.open_workingtree()
 
265
        >>> d2 = bzrdir.ScratchDir()
 
266
        >>> br2 = d2.open_branch()
 
267
        >>> wt2 = d2.open_workingtree()
271
268
        >>> br1.missing_revisions(br2)
272
269
        []
273
 
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
 
270
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
274
271
        >>> br1.missing_revisions(br2)
275
272
        [u'REVISION-ID-1']
276
273
        >>> br2.missing_revisions(br1)
277
274
        []
278
 
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
 
275
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
279
276
        >>> br1.missing_revisions(br2)
280
277
        []
281
 
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
 
278
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
282
279
        >>> br1.missing_revisions(br2)
283
280
        [u'REVISION-ID-2A']
284
 
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
 
281
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
285
282
        >>> br1.missing_revisions(br2)
286
283
        Traceback (most recent call last):
287
284
        DivergedBranches: These branches have diverged.  Try merge.
330
327
            raise bzrlib.errors.NoSuchRevision(self, revno)
331
328
        return history[revno - 1]
332
329
 
333
 
    def working_tree(self):
334
 
        """Return a `Tree` for the working copy if this is a local branch."""
335
 
        raise NotImplementedError('working_tree is abstract')
336
 
 
337
 
    def pull(self, source, overwrite=False):
 
330
    def pull(self, source, overwrite=False, stop_revision=None):
338
331
        raise NotImplementedError('pull is abstract')
339
332
 
340
333
    def basis_tree(self):
402
395
        """
403
396
        if revno < 1 or revno > self.revno():
404
397
            raise InvalidRevisionNumber(revno)
405
 
        
406
 
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
407
 
        """Copy this branch into the existing directory to_location.
408
 
 
409
 
        Returns the newly created branch object.
410
 
 
411
 
        revision
412
 
            If not None, only revisions up to this point will be copied.
413
 
            The head of the new branch will be that revision.  Must be a
414
 
            revid or None.
415
 
    
416
 
        to_location -- The destination directory; must either exist and be 
417
 
            empty, or not exist, in which case it is created.
418
 
    
419
 
        basis_branch
420
 
            A local branch to copy revisions from, related to this branch. 
421
 
            This is used when branching from a remote (slow) branch, and we have
422
 
            a local branch that might contain some relevant revisions.
423
 
    
424
 
        to_branch_type
425
 
            Branch type of destination branch
426
 
        """
427
 
        from bzrlib.workingtree import WorkingTree
428
 
        assert isinstance(to_location, basestring)
429
 
        if not bzrlib.osutils.lexists(to_location):
430
 
            os.mkdir(to_location)
431
 
        if to_branch_type is None:
432
 
            to_branch_type = BzrBranch
433
 
        print "FIXME use a branch format here"
434
 
        br_to = to_branch_type.initialize(to_location)
435
 
        mutter("copy branch from %s to %s", self, br_to)
436
 
        if basis_branch is not None:
437
 
            basis_branch.push_stores(br_to)
438
 
        if revision is None:
439
 
            revision = self.last_revision()
440
 
        br_to.update_revisions(self, stop_revision=revision)
441
 
        br_to.set_parent(self.base)
442
 
        WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
443
 
        mutter("copied")
444
 
        return br_to
445
 
 
446
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
447
 
        """ This function returns the file_id(s) involved in the
448
 
            changes between the from_revid revision and the to_revid
449
 
            revision
450
 
        """
451
 
        raise NotImplementedError('fileid_involved_between_revs is abstract')
452
 
 
453
 
    def fileid_involved(self, last_revid=None):
454
 
        """ This function returns the file_id(s) involved in the
455
 
            changes up to the revision last_revid
456
 
            If no parametr is passed, then all file_id[s] present in the
457
 
            repository are returned
458
 
        """
459
 
        raise NotImplementedError('fileid_involved is abstract')
460
 
 
461
 
    def fileid_involved_by_set(self, changes):
462
 
        """ This function returns the file_id(s) involved in the
463
 
            changes present in the set 'changes'
464
 
        """
465
 
        raise NotImplementedError('fileid_involved_by_set is abstract')
466
 
 
467
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
468
 
        """ This function returns the file_id(s) involved in the
469
 
            changes between the from_revid revision and the to_revid
470
 
            revision
471
 
        """
472
 
        raise NotImplementedError('fileid_involved_between_revs is abstract')
473
 
 
474
 
    def fileid_involved(self, last_revid=None):
475
 
        """ This function returns the file_id(s) involved in the
476
 
            changes up to the revision last_revid
477
 
            If no parametr is passed, then all file_id[s] present in the
478
 
            repository are returned
479
 
        """
480
 
        raise NotImplementedError('fileid_involved is abstract')
481
 
 
482
 
    def fileid_involved_by_set(self, changes):
483
 
        """ This function returns the file_id(s) involved in the
484
 
            changes present in the set 'changes'
485
 
        """
486
 
        raise NotImplementedError('fileid_involved_by_set is abstract')
487
 
 
488
 
class BzrBranchFormat(object):
 
398
 
 
399
    @needs_read_lock
 
400
    def clone(self, *args, **kwargs):
 
401
        """Clone this branch into to_bzrdir preserving all semantic values.
 
402
        
 
403
        revision_id: if not None, the revision history in the new branch will
 
404
                     be truncated to end with revision_id.
 
405
        """
 
406
        # for API compatability, until 0.8 releases we provide the old api:
 
407
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
408
        # after 0.8 releases, the *args and **kwargs should be changed:
 
409
        # def clone(self, to_bzrdir, revision_id=None):
 
410
        if (kwargs.get('to_location', None) or
 
411
            kwargs.get('revision', None) or
 
412
            kwargs.get('basis_branch', None) or
 
413
            (len(args) and isinstance(args[0], basestring))):
 
414
            # backwards compatability api:
 
415
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
416
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
417
            # get basis_branch
 
418
            if len(args) > 2:
 
419
                basis_branch = args[2]
 
420
            else:
 
421
                basis_branch = kwargs.get('basis_branch', None)
 
422
            if basis_branch:
 
423
                basis = basis_branch.bzrdir
 
424
            else:
 
425
                basis = None
 
426
            # get revision
 
427
            if len(args) > 1:
 
428
                revision_id = args[1]
 
429
            else:
 
430
                revision_id = kwargs.get('revision', None)
 
431
            # get location
 
432
            if len(args):
 
433
                url = args[0]
 
434
            else:
 
435
                # no default to raise if not provided.
 
436
                url = kwargs.get('to_location')
 
437
            return self.bzrdir.clone(url,
 
438
                                     revision_id=revision_id,
 
439
                                     basis=basis).open_branch()
 
440
        # new cleaner api.
 
441
        # generate args by hand 
 
442
        if len(args) > 1:
 
443
            revision_id = args[1]
 
444
        else:
 
445
            revision_id = kwargs.get('revision_id', None)
 
446
        if len(args):
 
447
            to_bzrdir = args[0]
 
448
        else:
 
449
            # no default to raise if not provided.
 
450
            to_bzrdir = kwargs.get('to_bzrdir')
 
451
        result = self._format.initialize(to_bzrdir)
 
452
        self.copy_content_into(result, revision_id=revision_id)
 
453
        return  result
 
454
 
 
455
    @needs_read_lock
 
456
    def sprout(self, to_bzrdir, revision_id=None):
 
457
        """Create a new line of development from the branch, into to_bzrdir.
 
458
        
 
459
        revision_id: if not None, the revision history in the new branch will
 
460
                     be truncated to end with revision_id.
 
461
        """
 
462
        result = self._format.initialize(to_bzrdir)
 
463
        self.copy_content_into(result, revision_id=revision_id)
 
464
        result.set_parent(self.bzrdir.root_transport.base)
 
465
        return result
 
466
 
 
467
    @needs_read_lock
 
468
    def copy_content_into(self, destination, revision_id=None):
 
469
        """Copy the content of self into destination.
 
470
 
 
471
        revision_id: if not None, the revision history in the new branch will
 
472
                     be truncated to end with revision_id.
 
473
        """
 
474
        new_history = self.revision_history()
 
475
        if revision_id is not None:
 
476
            try:
 
477
                new_history = new_history[:new_history.index(revision_id) + 1]
 
478
            except ValueError:
 
479
                rev = self.repository.get_revision(revision_id)
 
480
                new_history = rev.get_history(self.repository)[1:]
 
481
        destination.set_revision_history(new_history)
 
482
        parent = self.get_parent()
 
483
        if parent:
 
484
            destination.set_parent(parent)
 
485
 
 
486
 
 
487
class BranchFormat(object):
489
488
    """An encapsulation of the initialization and open routines for a format.
490
489
 
491
490
    Formats provide three things:
503
502
    object will be created every time regardless.
504
503
    """
505
504
 
 
505
    _default_format = None
 
506
    """The default format used for new branches."""
 
507
 
506
508
    _formats = {}
507
509
    """The known formats."""
508
510
 
509
511
    @classmethod
510
 
    def find_format(klass, transport):
511
 
        """Return the format registered for URL."""
 
512
    def find_format(klass, a_bzrdir):
 
513
        """Return the format for the branch object in a_bzrdir."""
512
514
        try:
513
 
            format_string = transport.get(".bzr/branch-format").read()
 
515
            transport = a_bzrdir.get_branch_transport(None)
 
516
            format_string = transport.get("format").read()
514
517
            return klass._formats[format_string]
515
518
        except NoSuchFile:
516
519
            raise NotBranchError(path=transport.base)
517
520
        except KeyError:
518
521
            raise errors.UnknownFormatError(format_string)
519
522
 
 
523
    @classmethod
 
524
    def get_default_format(klass):
 
525
        """Return the current default format."""
 
526
        return klass._default_format
 
527
 
520
528
    def get_format_string(self):
521
529
        """Return the ASCII format string that identifies this format."""
522
530
        raise NotImplementedError(self.get_format_string)
523
531
 
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)
 
532
    def initialize(self, a_bzrdir):
 
533
        """Create a branch of this format in a_bzrdir."""
 
534
        raise NotImplementedError(self.initialized)
594
535
 
595
536
    def is_supported(self):
596
537
        """Is this format supported?
601
542
        """
602
543
        return True
603
544
 
604
 
    def open(self, transport):
605
 
        """Fill out the data in branch for the branch at url."""
606
 
        return BzrBranch(transport, _format=self)
 
545
    def open(self, a_bzrdir, _found=False):
 
546
        """Return the branch object for a_bzrdir
 
547
 
 
548
        _found is a private parameter, do not use it. It is used to indicate
 
549
               if format probing has already be done.
 
550
        """
 
551
        raise NotImplementedError(self.open)
607
552
 
608
553
    @classmethod
609
554
    def register_format(klass, format):
610
555
        klass._formats[format.get_format_string()] = format
611
556
 
612
557
    @classmethod
 
558
    def set_default_format(klass, format):
 
559
        klass._default_format = format
 
560
 
 
561
    @classmethod
613
562
    def unregister_format(klass, format):
614
563
        assert klass._formats[format.get_format_string()] is format
615
564
        del klass._formats[format.get_format_string()]
616
565
 
617
566
 
618
 
class BzrBranchFormat4(BzrBranchFormat):
 
567
class BzrBranchFormat4(BranchFormat):
619
568
    """Bzr branch format 4.
620
569
 
621
570
    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.
 
571
     - a revision-history file.
 
572
     - a branch-lock lock file [ to be shared with the bzrdir ]
627
573
    """
628
574
 
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.
 
575
    def initialize(self, a_bzrdir):
 
576
        """Create a branch of this format in a_bzrdir."""
 
577
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
578
        branch_transport = a_bzrdir.get_branch_transport(self)
 
579
        utf8_files = [('revision-history', ''),
 
580
                      ('branch-name', ''),
 
581
                      ]
 
582
        control_files = LockableFiles(branch_transport, 'branch-lock')
 
583
        control_files.lock_write()
 
584
        try:
 
585
            for file, content in utf8_files:
 
586
                control_files.put_utf8(file, content)
 
587
        finally:
 
588
            control_files.unlock()
 
589
        return self.open(a_bzrdir, _found=True)
 
590
 
 
591
    def __init__(self):
 
592
        super(BzrBranchFormat4, self).__init__()
 
593
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
594
 
 
595
    def open(self, a_bzrdir, _found=False):
 
596
        """Return the branch object for a_bzrdir
 
597
 
 
598
        _found is a private parameter, do not use it. It is used to indicate
 
599
               if format probing has already be done.
643
600
        """
644
 
        return False
645
 
 
646
 
 
647
 
class BzrBranchFormat5(BzrBranchFormat):
 
601
        if not _found:
 
602
            # we are being called directly and must probe.
 
603
            raise NotImplementedError
 
604
        return BzrBranch(_format=self,
 
605
                         _control_files=a_bzrdir._control_files,
 
606
                         a_bzrdir=a_bzrdir,
 
607
                         _repository=a_bzrdir.open_repository())
 
608
 
 
609
 
 
610
class BzrBranchFormat5(BranchFormat):
648
611
    """Bzr branch format 5.
649
612
 
650
613
    This format has:
651
 
     - weaves for file texts and inventory
652
 
     - flat stores
653
 
     - TextStores for revisions and signatures.
 
614
     - a revision-history file.
 
615
     - a format string
 
616
     - a lock file.
 
617
     - works with shared repositories.
654
618
    """
655
619
 
656
620
    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.
 
621
        """See BranchFormat.get_format_string()."""
 
622
        return "Bazaar-NG branch format 5\n"
 
623
        
 
624
    def initialize(self, a_bzrdir):
 
625
        """Create a branch of this format in a_bzrdir."""
 
626
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
627
        branch_transport = a_bzrdir.get_branch_transport(self)
 
628
 
 
629
        utf8_files = [('revision-history', ''),
 
630
                      ('branch-name', ''),
 
631
                      ]
 
632
        lock_file = 'lock'
 
633
        branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
 
634
        control_files = LockableFiles(branch_transport, 'lock')
 
635
        control_files.lock_write()
 
636
        control_files.put_utf8('format', self.get_format_string())
 
637
        try:
 
638
            for file, content in utf8_files:
 
639
                control_files.put_utf8(file, content)
 
640
        finally:
 
641
            control_files.unlock()
 
642
        return self.open(a_bzrdir, _found=True, )
 
643
 
 
644
    def __init__(self):
 
645
        super(BzrBranchFormat5, self).__init__()
 
646
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
647
 
 
648
    def open(self, a_bzrdir, _found=False):
 
649
        """Return the branch object for a_bzrdir
 
650
 
 
651
        _found is a private parameter, do not use it. It is used to indicate
 
652
               if format probing has already be done.
 
653
        """
 
654
        if not _found:
 
655
            format = BranchFormat.find_format(a_bzrdir)
 
656
            assert format.__class__ == self.__class__
 
657
        transport = a_bzrdir.get_branch_transport(None)
 
658
        control_files = LockableFiles(transport, 'lock')
 
659
        return BzrBranch(_format=self,
 
660
                         _control_files=control_files,
 
661
                         a_bzrdir=a_bzrdir,
 
662
                         _repository=a_bzrdir.find_repository())
 
663
 
 
664
 
 
665
class BranchReferenceFormat(BranchFormat):
 
666
    """Bzr branch reference format.
 
667
 
 
668
    Branch references are used in implementing checkouts, they
 
669
    act as an alias to the real branch which is at some other url.
663
670
 
664
671
    This format has:
665
 
     - weaves for file texts and inventory
666
 
     - hash subdirectory based stores.
667
 
     - TextStores for revisions and signatures.
 
672
     - A location file
 
673
     - a format string
668
674
    """
669
675
 
670
676
    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
 
 
 
677
        """See BranchFormat.get_format_string()."""
 
678
        return "Bazaar-NG Branch Reference Format 1\n"
 
679
        
 
680
    def initialize(self, a_bzrdir, target_branch=None):
 
681
        """Create a branch of this format in a_bzrdir."""
 
682
        if target_branch is None:
 
683
            # this format does not implement branch itself, thus the implicit
 
684
            # creation contract must see it as uninitializable
 
685
            raise errors.UninitializableFormat(self)
 
686
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
687
        branch_transport = a_bzrdir.get_branch_transport(self)
 
688
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
689
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
690
        branch_transport.put('format', StringIO(self.get_format_string()))
 
691
        return self.open(a_bzrdir, _found=True)
 
692
 
 
693
    def __init__(self):
 
694
        super(BranchReferenceFormat, self).__init__()
 
695
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
696
 
 
697
    def _make_reference_clone_function(format, a_branch):
 
698
        """Create a clone() routine for a branch dynamically."""
 
699
        def clone(to_bzrdir, revision_id=None):
 
700
            """See Branch.clone()."""
 
701
            return format.initialize(to_bzrdir, a_branch)
 
702
            # cannot obey revision_id limits when cloning a reference ...
 
703
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
704
            # emit some sort of warning/error to the caller ?!
 
705
        return clone
 
706
 
 
707
    def open(self, a_bzrdir, _found=False):
 
708
        """Return the branch that the branch reference in a_bzrdir points at.
 
709
 
 
710
        _found is a private parameter, do not use it. It is used to indicate
 
711
               if format probing has already be done.
 
712
        """
 
713
        if not _found:
 
714
            format = BranchFormat.find_format(a_bzrdir)
 
715
            assert format.__class__ == self.__class__
 
716
        transport = a_bzrdir.get_branch_transport(None)
 
717
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
718
        result = real_bzrdir.open_branch()
 
719
        # this changes the behaviour of result.clone to create a new reference
 
720
        # rather than a copy of the content of the branch.
 
721
        # I did not use a proxy object because that needs much more extensive
 
722
        # testing, and we are only changing one behaviour at the moment.
 
723
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
724
        # then this should be refactored to introduce a tested proxy branch
 
725
        # and a subclass of that for use in overriding clone() and ....
 
726
        # - RBC 20060210
 
727
        result.clone = self._make_reference_clone_function(result)
 
728
        return result
 
729
 
 
730
 
 
731
# formats which have no format string are not discoverable
 
732
# and not independently creatable, so are not registered.
 
733
__default_format = BzrBranchFormat5()
 
734
BranchFormat.register_format(__default_format)
 
735
BranchFormat.register_format(BranchReferenceFormat())
 
736
BranchFormat.set_default_format(__default_format)
 
737
_legacy_formats = [BzrBranchFormat4(),
 
738
                   ]
683
739
 
684
740
class BzrBranch(Branch):
685
741
    """A branch stored in the actual filesystem.
687
743
    Note that it's "local" in the context of the filesystem; it doesn't
688
744
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
689
745
    it's writable, and can be accessed via the normal filesystem API.
690
 
 
691
746
    """
692
747
    # We actually expect this class to be somewhat short-lived; part of its
693
748
    # purpose is to try to isolate what bits of the branch logic are tied to
700
755
    # This should match a prefix with a function which accepts
701
756
    REVISION_NAMESPACES = {}
702
757
 
703
 
    def push_stores(self, branch_to):
704
 
        """See Branch.push_stores."""
705
 
        if (not isinstance(self._branch_format, BzrBranchFormat4) or
706
 
            self._branch_format != branch_to._branch_format):
707
 
            from bzrlib.fetch import greedy_fetch
708
 
            mutter("Using fetch logic to push between %s(%s) and %s(%s)",
709
 
                   self, self._branch_format, branch_to, branch_to._branch_format)
710
 
            greedy_fetch(to_branch=branch_to, from_branch=self,
711
 
                         revision=self.last_revision())
712
 
            return
713
 
 
714
 
        # format 4 to format 4 logic only.
715
 
        store_pairs = ((self.text_store,      branch_to.text_store),
716
 
                       (self.inventory_store, branch_to.inventory_store),
717
 
                       (self.revision_store,  branch_to.revision_store))
718
 
        try:
719
 
            for from_store, to_store in store_pairs: 
720
 
                copy_all(from_store, to_store)
721
 
        except UnlistableStore:
722
 
            raise UnlistableBranch(from_store)
723
 
 
724
 
    def __init__(self, transport, init=DEPRECATED_PARAMETER,
 
758
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
725
759
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
726
 
                 _control_files=None):
 
760
                 _control_files=None, a_bzrdir=None, _repository=None):
727
761
        """Create new branch object at a particular location.
728
762
 
729
763
        transport -- A Transport object, defining how to access files.
736
770
            version is not applied.  This is intended only for
737
771
            upgrade/recovery type use; it's not guaranteed that
738
772
            all operations will work on old format branches.
739
 
 
740
 
        In the test suite, creation of new trees is tested using the
741
 
        `ScratchBranch` class.
742
773
        """
743
 
        assert isinstance(transport, Transport), \
744
 
            "%r is not a Transport" % transport
745
 
        self._transport = transport
 
774
        if a_bzrdir is None:
 
775
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
776
        else:
 
777
            self.bzrdir = a_bzrdir
 
778
        self._transport = self.bzrdir.transport.clone('..')
746
779
        self._base = self._transport.base
 
780
        self._format = _format
747
781
        if _control_files is None:
748
 
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
749
 
                                           'branch-lock')
 
782
            raise BzrBadParameterMissing('_control_files')
750
783
        self.control_files = _control_files
751
784
        if deprecated_passed(init):
752
785
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
761
794
        if deprecated_passed(relax_version_check):
762
795
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
763
796
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
764
 
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
797
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
765
798
                 "open() method.",
766
799
                 DeprecationWarning,
767
800
                 stacklevel=2)
768
801
            if (not relax_version_check
769
 
                and not self._branch_format.is_supported()):
 
802
                and not self._format.is_supported()):
770
803
                raise errors.UnsupportedFormatError(
771
804
                        'sorry, branch format %r not supported' % fmt,
772
805
                        ['use a different bzr version',
773
806
                         'or remove the .bzr directory'
774
807
                         ' and "bzr init" again'])
775
 
        self.repository = Repository(transport, self._branch_format)
776
 
 
777
 
 
778
 
    @staticmethod
779
 
    def _initialize(base):
780
 
        """Create a bzr branch in the latest format."""
781
 
        return BzrBranchFormat6().initialize(base)
 
808
        if deprecated_passed(transport):
 
809
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
810
                 "parameter is deprecated as of bzr 0.8. "
 
811
                 "Please use Branch.open, or bzrdir.open_branch().",
 
812
                 DeprecationWarning,
 
813
                 stacklevel=2)
 
814
        self.repository = _repository
782
815
 
783
816
    def __str__(self):
784
817
        return '%s(%r)' % (self.__class__.__name__, self.base)
836
869
        """Identify the branch format if needed.
837
870
 
838
871
        The format is stored as a reference to the format object in
839
 
        self._branch_format for code that needs to check it later.
 
872
        self._format for code that needs to check it later.
840
873
 
841
874
        The format parameter is either None or the branch format class
842
875
        used to open this branch.
 
876
 
 
877
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
843
878
        """
844
879
        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)
 
880
            format = BzrBranchFormat.find_format(self.bzrdir)
 
881
        self._format = format
 
882
        mutter("got branch format %s", self._format)
848
883
 
849
884
    @needs_read_lock
850
885
    def get_root_id(self):
932
967
 
933
968
    def update_revisions(self, other, stop_revision=None):
934
969
        """See Branch.update_revisions."""
935
 
        from bzrlib.fetch import greedy_fetch
936
970
        if stop_revision is None:
937
971
            stop_revision = other.last_revision()
938
972
        ### Should this be checking is_ancestor instead of revision_history?
939
973
        if (stop_revision is not None and 
940
974
            stop_revision in self.revision_history()):
941
975
            return
942
 
        greedy_fetch(to_branch=self, from_branch=other,
943
 
                     revision=stop_revision)
 
976
        self.fetch(other, stop_revision)
944
977
        pullable_revs = self.pullable_revisions(other, stop_revision)
945
978
        if len(pullable_revs) > 0:
946
979
            self.append_revision(*pullable_revs)
965
998
        
966
999
    def basis_tree(self):
967
1000
        """See Branch.basis_tree."""
968
 
        try:
969
 
            revision_id = self.revision_history()[-1]
970
 
            # FIXME: This is an abstraction violation, the basis tree 
971
 
            # here as defined is on the working tree, the method should
972
 
            # be too. The basis tree for a branch can be different than
973
 
            # that for a working tree. RBC 20051207
974
 
            xml = self.working_tree().read_basis_inventory(revision_id)
975
 
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
976
 
            return RevisionTree(self.repository, inv, revision_id)
977
 
        except (IndexError, NoSuchFile, NoWorkingTree), e:
978
 
            return self.repository.revision_tree(self.last_revision())
 
1001
        return self.repository.revision_tree(self.last_revision())
979
1002
 
 
1003
    @deprecated_method(zero_eight)
980
1004
    def working_tree(self):
981
 
        """See Branch.working_tree."""
 
1005
        """Create a Working tree object for this branch."""
982
1006
        from bzrlib.workingtree import WorkingTree
983
1007
        from bzrlib.transport.local import LocalTransport
984
1008
        if (self.base.find('://') != -1 or 
985
1009
            not isinstance(self._transport, LocalTransport)):
986
1010
            raise NoWorkingTree(self.base)
987
 
        return WorkingTree(self.base, branch=self)
 
1011
        return self.bzrdir.open_workingtree()
988
1012
 
989
1013
    @needs_write_lock
990
 
    def pull(self, source, overwrite=False):
 
1014
    def pull(self, source, overwrite=False, stop_revision=None):
991
1015
        """See Branch.pull."""
992
1016
        source.lock_read()
993
1017
        try:
994
1018
            old_count = len(self.revision_history())
995
1019
            try:
996
 
                self.update_revisions(source)
 
1020
                self.update_revisions(source,stop_revision)
997
1021
            except DivergedBranches:
998
1022
                if not overwrite:
999
1023
                    raise
1060
1084
        history = self._get_truncated_history(revision)
1061
1085
        if not bzrlib.osutils.lexists(to_location):
1062
1086
            os.mkdir(to_location)
1063
 
        branch_to = Branch.initialize(to_location)
 
1087
        bzrdir_to = self.bzrdir._format.initialize(to_location)
 
1088
        self.repository.clone(bzrdir_to)
 
1089
        branch_to = bzrdir_to.create_branch()
1064
1090
        mutter("copy branch from %s to %s", self, branch_to)
1065
1091
 
1066
 
        self.repository.copy(branch_to.repository)
1067
 
        
1068
 
        # must be done *after* history is copied across
1069
1092
        # FIXME duplicate code with base .clone().
1070
1093
        # .. would template method be useful here?  RBC 20051207
1071
1094
        branch_to.set_parent(self.base)
1072
1095
        branch_to.append_revision(*history)
1073
 
        # FIXME: this should be in workingtree.clone
1074
 
        WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
 
1096
        WorkingTree.create(branch_to, branch_to.base)
1075
1097
        mutter("copied")
1076
1098
        return branch_to
1077
1099
 
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."
1080
 
        if to_branch_type is None:
1081
 
            to_branch_type = BzrBranch
1082
 
 
1083
 
        if to_branch_type == BzrBranch \
1084
 
            and self.repository.weave_store.listable() \
1085
 
            and self.repository.revision_store.listable():
1086
 
            return self._clone_weave(to_location, revision, basis_branch)
1087
 
 
1088
 
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1089
 
 
1090
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
1091
 
        """Find file_id(s) which are involved in the changes between revisions.
1092
 
 
1093
 
        This determines the set of revisions which are involved, and then
1094
 
        finds all file ids affected by those revisions.
1095
 
        """
1096
 
        # TODO: jam 20060119 This code assumes that w.inclusions will
1097
 
        #       always be correct. But because of the presence of ghosts
1098
 
        #       it is possible to be wrong.
1099
 
        #       One specific example from Robert Collins:
1100
 
        #       Two branches, with revisions ABC, and AD
1101
 
        #       C is a ghost merge of D.
1102
 
        #       Inclusions doesn't recognize D as an ancestor.
1103
 
        #       If D is ever merged in the future, the weave
1104
 
        #       won't be fixed, because AD never saw revision C
1105
 
        #       to cause a conflict which would force a reweave.
1106
 
        w = self.repository.get_inventory_weave()
1107
 
        from_set = set(w.inclusions([w.lookup(from_revid)]))
1108
 
        to_set = set(w.inclusions([w.lookup(to_revid)]))
1109
 
        included = to_set.difference(from_set)
1110
 
        changed = map(w.idx_to_name, included)
1111
 
        return self._fileid_involved_by_set(changed)
1112
 
 
1113
 
    def fileid_involved(self, last_revid=None):
1114
 
        """Find all file_ids modified in the ancestry of last_revid.
1115
 
 
1116
 
        :param last_revid: If None, last_revision() will be used.
1117
 
        """
1118
 
        w = self.repository.get_inventory_weave()
1119
 
        if not last_revid:
1120
 
            changed = set(w._names)
1121
 
        else:
1122
 
            included = w.inclusions([w.lookup(last_revid)])
1123
 
            changed = map(w.idx_to_name, included)
1124
 
        return self._fileid_involved_by_set(changed)
1125
 
 
1126
 
    def fileid_involved_by_set(self, changes):
1127
 
        """Find all file_ids modified by the set of revisions passed in.
1128
 
 
1129
 
        :param changes: A set() of revision ids
1130
 
        """
1131
 
        # TODO: jam 20060119 This line does *nothing*, remove it.
1132
 
        #       or better yet, change _fileid_involved_by_set so
1133
 
        #       that it takes the inventory weave, rather than
1134
 
        #       pulling it out by itself.
1135
 
        w = self.repository.get_inventory_weave()
1136
 
        return self._fileid_involved_by_set(changes)
1137
 
 
1138
 
    def _fileid_involved_by_set(self, changes):
1139
 
        """Find the set of file-ids affected by the set of revisions.
1140
 
 
1141
 
        :param changes: A set() of revision ids.
1142
 
        :return: A set() of file ids.
1143
 
        
1144
 
        This peaks at the Weave, interpreting each line, looking to
1145
 
        see if it mentions one of the revisions. And if so, includes
1146
 
        the file id mentioned.
1147
 
        This expects both the Weave format, and the serialization
1148
 
        to have a single line per file/directory, and to have
1149
 
        fileid="" and revision="" on that line.
1150
 
        """
1151
 
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
1152
 
                isinstance(self._branch_format, BzrBranchFormat6)), \
1153
 
            "fileid_involved only supported for branches which store inventory as xml"
1154
 
 
1155
 
        w = self.repository.get_inventory_weave()
1156
 
        file_ids = set()
1157
 
        for line in w._weave:
1158
 
 
1159
 
            # it is ugly, but it is due to the weave structure
1160
 
            if not isinstance(line, basestring): continue
1161
 
 
1162
 
            start = line.find('file_id="')+9
1163
 
            if start < 9: continue
1164
 
            end = line.find('"', start)
1165
 
            assert end>= 0
1166
 
            file_id = xml.sax.saxutils.unescape(line[start:end])
1167
 
 
1168
 
            # check if file_id is already present
1169
 
            if file_id in file_ids: continue
1170
 
 
1171
 
            start = line.find('revision="')+10
1172
 
            if start < 10: continue
1173
 
            end = line.find('"', start)
1174
 
            assert end>= 0
1175
 
            revision_id = xml.sax.saxutils.unescape(line[start:end])
1176
 
 
1177
 
            if revision_id in changes:
1178
 
                file_ids.add(file_id)
1179
 
 
1180
 
        return file_ids
1181
 
 
1182
 
 
1183
 
Branch.set_default_initializer(BzrBranch._initialize)
1184
 
 
1185
1100
 
1186
1101
class BranchTestProviderAdapter(object):
1187
1102
    """A tool to generate a suite testing multiple branch formats at once.
1199
1114
    
1200
1115
    def adapt(self, test):
1201
1116
        result = TestSuite()
1202
 
        for format in self._formats:
 
1117
        for branch_format, bzrdir_format in self._formats:
1203
1118
            new_test = deepcopy(test)
1204
1119
            new_test.transport_server = self._transport_server
1205
1120
            new_test.transport_readonly_server = self._transport_readonly_server
1206
 
            new_test.branch_format = format
 
1121
            new_test.bzrdir_format = bzrdir_format
 
1122
            new_test.branch_format = branch_format
1207
1123
            def make_new_test_id():
1208
 
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1124
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1209
1125
                return lambda: new_id
1210
1126
            new_test.id = make_new_test_id()
1211
1127
            result.addTest(new_test)
1212
1128
        return result
1213
1129
 
1214
1130
 
1215
 
class ScratchBranch(BzrBranch):
1216
 
    """Special test class: a branch that cleans up after itself.
1217
 
 
1218
 
    >>> b = ScratchBranch()
1219
 
    >>> isdir(b.base)
1220
 
    True
1221
 
    >>> bd = b.base
1222
 
    >>> b._transport.__del__()
1223
 
    >>> isdir(bd)
1224
 
    False
1225
 
    """
1226
 
 
1227
 
    def __init__(self, files=[], dirs=[], transport=None):
1228
 
        """Make a test branch.
1229
 
 
1230
 
        This creates a temporary directory and runs init-tree in it.
1231
 
 
1232
 
        If any files are listed, they are created in the working copy.
1233
 
        """
1234
 
        if transport is None:
1235
 
            transport = bzrlib.transport.local.ScratchTransport()
1236
 
            # local import for scope restriction
1237
 
            from bzrlib.workingtree import WorkingTree
1238
 
            WorkingTree.create_standalone(transport.base)
1239
 
            super(ScratchBranch, self).__init__(transport)
1240
 
        else:
1241
 
            super(ScratchBranch, self).__init__(transport)
1242
 
 
1243
 
        # BzrBranch creates a clone to .bzr and then forgets about the
1244
 
        # original transport. A ScratchTransport() deletes itself and
1245
 
        # everything underneath it when it goes away, so we need to
1246
 
        # grab a local copy to prevent that from happening
1247
 
        self._transport = transport
1248
 
 
1249
 
        for d in dirs:
1250
 
            self._transport.mkdir(d)
1251
 
            
1252
 
        for f in files:
1253
 
            self._transport.put(f, 'content of %s' % f)
1254
 
 
1255
 
    def clone(self):
1256
 
        """
1257
 
        >>> orig = ScratchBranch(files=["file1", "file2"])
1258
 
        >>> os.listdir(orig.base)
1259
 
        [u'.bzr', u'file1', u'file2']
1260
 
        >>> clone = orig.clone()
1261
 
        >>> if os.name != 'nt':
1262
 
        ...   os.path.samefile(orig.base, clone.base)
1263
 
        ... else:
1264
 
        ...   orig.base == clone.base
1265
 
        ...
1266
 
        False
1267
 
        >>> os.listdir(clone.base)
1268
 
        [u'.bzr', u'file1', u'file2']
1269
 
        """
1270
 
        from shutil import copytree
1271
 
        from bzrlib.osutils import mkdtemp
1272
 
        base = mkdtemp()
1273
 
        os.rmdir(base)
1274
 
        copytree(self.base, base, symlinks=True)
1275
 
        return ScratchBranch(
1276
 
            transport=bzrlib.transport.local.ScratchTransport(base))
1277
 
    
1278
 
 
1279
1131
######################################################################
1280
1132
# predicates
1281
1133
 
1282
1134
 
1283
 
def is_control_file(filename):
1284
 
    ## FIXME: better check
1285
 
    filename = normpath(filename)
1286
 
    while filename != '':
1287
 
        head, tail = os.path.split(filename)
1288
 
        ## mutter('check %r for control file' % ((head, tail),))
1289
 
        if tail == bzrlib.BZRDIR:
1290
 
            return True
1291
 
        if filename == head:
1292
 
            break
1293
 
        filename = head
1294
 
    return False
 
1135
@deprecated_function(zero_eight)
 
1136
def ScratchBranch(*args, **kwargs):
 
1137
    """See bzrlib.bzrdir.ScratchDir."""
 
1138
    d = ScratchDir(*args, **kwargs)
 
1139
    return d.open_branch()
 
1140
 
 
1141
 
 
1142
@deprecated_function(zero_eight)
 
1143
def is_control_file(*args, **kwargs):
 
1144
    """See bzrlib.workingtree.is_control_file."""
 
1145
    return bzrlib.workingtree.is_control_file(*args, **kwargs)