~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-02-18 02:33:47 UTC
  • mfrom: (1534.1.24 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060218023347-0952c65f668bfd68
Merge Robert Collins integration.

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
94
94
    # - RBC 20060112
95
95
    base = None
96
96
 
97
 
    _default_initializer = None
98
 
    """The default initializer for making new branches."""
 
97
    @staticmethod
 
98
    def create(base):
 
99
        """Construct the current default format branch in a_bzrdir.
 
100
 
 
101
        This creates the current default BzrDir format, and if that 
 
102
        supports multiple Branch formats, then the default Branch format
 
103
        will take effect.
 
104
        """
 
105
        print "not usable until we have repositories"
 
106
        raise NotImplementedError("not usable right now")
 
107
        return bzrdir.BzrDir.create(base)
99
108
 
100
109
    def __init__(self, *ignored, **ignored_too):
101
110
        raise NotImplementedError('The Branch class is abstract')
102
111
 
103
112
    @staticmethod
 
113
    @deprecated_method(zero_eight)
104
114
    def open_downlevel(base):
105
115
        """Open a branch which may be of an old format."""
106
116
        return Branch.open(base, _unsupported=True)
107
117
        
108
118
    @staticmethod
109
119
    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.
 
120
        """Open the repository rooted at base.
 
121
 
 
122
        For instance, if the repository is at URL/.bzr/repository,
 
123
        Repository.open(URL) -> a Repository instance.
113
124
        """
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)
 
125
        control = bzrdir.BzrDir.open(base, _unsupported)
 
126
        return control.open_branch(_unsupported)
125
127
 
126
128
    @staticmethod
127
129
    def open_containing(url):
135
137
        format, UnknownFormatError or UnsupportedFormatError are raised.
136
138
        If there is one, it is returned, along with the unused portion of url.
137
139
        """
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))
 
140
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
141
        return control.open_branch(), relpath
161
142
 
162
143
    @staticmethod
163
144
    @deprecated_function(zero_eight)
167
148
        NOTE: This will soon be deprecated in favour of creation
168
149
        through a BzrDir.
169
150
        """
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)
 
151
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
183
152
 
184
153
    def setup_caching(self, cache_root):
185
154
        """Subclasses that care about caching should override this, and set
200
169
 
201
170
    nick = property(_get_nick, _set_nick)
202
171
        
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
172
    def lock_write(self):
208
173
        raise NotImplementedError('lock_write is abstract')
209
174
        
265
230
        If self and other have not diverged, return a list of the revisions
266
231
        present in other, but missing from self.
267
232
 
 
233
        >>> from bzrlib.workingtree import WorkingTree
268
234
        >>> bzrlib.trace.silent = True
269
 
        >>> br1 = ScratchBranch()
270
 
        >>> br2 = ScratchBranch()
 
235
        >>> d1 = bzrdir.ScratchDir()
 
236
        >>> br1 = d1.open_branch()
 
237
        >>> wt1 = d1.open_workingtree()
 
238
        >>> d2 = bzrdir.ScratchDir()
 
239
        >>> br2 = d2.open_branch()
 
240
        >>> wt2 = d2.open_workingtree()
271
241
        >>> br1.missing_revisions(br2)
272
242
        []
273
 
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
 
243
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
274
244
        >>> br1.missing_revisions(br2)
275
245
        [u'REVISION-ID-1']
276
246
        >>> br2.missing_revisions(br1)
277
247
        []
278
 
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
 
248
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
279
249
        >>> br1.missing_revisions(br2)
280
250
        []
281
 
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
 
251
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
282
252
        >>> br1.missing_revisions(br2)
283
253
        [u'REVISION-ID-2A']
284
 
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
 
254
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
285
255
        >>> br1.missing_revisions(br2)
286
256
        Traceback (most recent call last):
287
257
        DivergedBranches: These branches have diverged.  Try merge.
330
300
            raise bzrlib.errors.NoSuchRevision(self, revno)
331
301
        return history[revno - 1]
332
302
 
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):
 
303
    def pull(self, source, overwrite=False, stop_revision=None):
338
304
        raise NotImplementedError('pull is abstract')
339
305
 
340
306
    def basis_tree(self):
402
368
        """
403
369
        if revno < 1 or revno > self.revno():
404
370
            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):
 
371
 
 
372
    @needs_read_lock
 
373
    def clone(self, *args, **kwargs):
 
374
        """Clone this branch into to_bzrdir preserving all semantic values.
 
375
        
 
376
        revision_id: if not None, the revision history in the new branch will
 
377
                     be truncated to end with revision_id.
 
378
        """
 
379
        # for API compatability, until 0.8 releases we provide the old api:
 
380
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
381
        # after 0.8 releases, the *args and **kwargs should be changed:
 
382
        # def clone(self, to_bzrdir, revision_id=None):
 
383
        if (kwargs.get('to_location', None) or
 
384
            kwargs.get('revision', None) or
 
385
            kwargs.get('basis_branch', None) or
 
386
            (len(args) and isinstance(args[0], basestring))):
 
387
            # backwards compatability api:
 
388
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
389
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
390
            # get basis_branch
 
391
            if len(args) > 2:
 
392
                basis_branch = args[2]
 
393
            else:
 
394
                basis_branch = kwargs.get('basis_branch', None)
 
395
            if basis_branch:
 
396
                basis = basis_branch.bzrdir
 
397
            else:
 
398
                basis = None
 
399
            # get revision
 
400
            if len(args) > 1:
 
401
                revision_id = args[1]
 
402
            else:
 
403
                revision_id = kwargs.get('revision', None)
 
404
            # get location
 
405
            if len(args):
 
406
                url = args[0]
 
407
            else:
 
408
                # no default to raise if not provided.
 
409
                url = kwargs.get('to_location')
 
410
            return self.bzrdir.clone(url,
 
411
                                     revision_id=revision_id,
 
412
                                     basis=basis).open_branch()
 
413
        # new cleaner api.
 
414
        # generate args by hand 
 
415
        if len(args) > 1:
 
416
            revision_id = args[1]
 
417
        else:
 
418
            revision_id = kwargs.get('revision_id', None)
 
419
        if len(args):
 
420
            to_bzrdir = args[0]
 
421
        else:
 
422
            # no default to raise if not provided.
 
423
            to_bzrdir = kwargs.get('to_bzrdir')
 
424
        result = self._format.initialize(to_bzrdir)
 
425
        self.copy_content_into(result, revision_id=revision_id)
 
426
        return  result
 
427
 
 
428
    @needs_read_lock
 
429
    def sprout(self, to_bzrdir, revision_id=None):
 
430
        """Create a new line of development from the branch, into to_bzrdir.
 
431
        
 
432
        revision_id: if not None, the revision history in the new branch will
 
433
                     be truncated to end with revision_id.
 
434
        """
 
435
        result = self._format.initialize(to_bzrdir)
 
436
        self.copy_content_into(result, revision_id=revision_id)
 
437
        result.set_parent(self.bzrdir.root_transport.base)
 
438
        return result
 
439
 
 
440
    @needs_read_lock
 
441
    def copy_content_into(self, destination, revision_id=None):
 
442
        """Copy the content of self into destination.
 
443
 
 
444
        revision_id: if not None, the revision history in the new branch will
 
445
                     be truncated to end with revision_id.
 
446
        """
 
447
        new_history = self.revision_history()
 
448
        if revision_id is not None:
 
449
            try:
 
450
                new_history = new_history[:new_history.index(revision_id) + 1]
 
451
            except ValueError:
 
452
                rev = self.repository.get_revision(revision_id)
 
453
                new_history = rev.get_history(self.repository)[1:]
 
454
        destination.set_revision_history(new_history)
 
455
        parent = self.get_parent()
 
456
        if parent:
 
457
            destination.set_parent(parent)
 
458
 
 
459
 
 
460
class BranchFormat(object):
489
461
    """An encapsulation of the initialization and open routines for a format.
490
462
 
491
463
    Formats provide three things:
503
475
    object will be created every time regardless.
504
476
    """
505
477
 
 
478
    _default_format = None
 
479
    """The default format used for new branches."""
 
480
 
506
481
    _formats = {}
507
482
    """The known formats."""
508
483
 
509
484
    @classmethod
510
 
    def find_format(klass, transport):
511
 
        """Return the format registered for URL."""
 
485
    def find_format(klass, a_bzrdir):
 
486
        """Return the format for the branch object in a_bzrdir."""
512
487
        try:
513
 
            format_string = transport.get(".bzr/branch-format").read()
 
488
            transport = a_bzrdir.get_branch_transport(None)
 
489
            format_string = transport.get("format").read()
514
490
            return klass._formats[format_string]
515
491
        except NoSuchFile:
516
492
            raise NotBranchError(path=transport.base)
517
493
        except KeyError:
518
494
            raise errors.UnknownFormatError(format_string)
519
495
 
 
496
    @classmethod
 
497
    def get_default_format(klass):
 
498
        """Return the current default format."""
 
499
        return klass._default_format
 
500
 
520
501
    def get_format_string(self):
521
502
        """Return the ASCII format string that identifies this format."""
522
503
        raise NotImplementedError(self.get_format_string)
523
504
 
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)
 
505
    def initialize(self, a_bzrdir):
 
506
        """Create a branch of this format in a_bzrdir."""
 
507
        raise NotImplementedError(self.initialized)
594
508
 
595
509
    def is_supported(self):
596
510
        """Is this format supported?
601
515
        """
602
516
        return True
603
517
 
604
 
    def open(self, transport):
605
 
        """Fill out the data in branch for the branch at url."""
606
 
        return BzrBranch(transport, _format=self)
 
518
    def open(self, a_bzrdir, _found=False):
 
519
        """Return the branch object for a_bzrdir
 
520
 
 
521
        _found is a private parameter, do not use it. It is used to indicate
 
522
               if format probing has already be done.
 
523
        """
 
524
        raise NotImplementedError(self.open)
607
525
 
608
526
    @classmethod
609
527
    def register_format(klass, format):
610
528
        klass._formats[format.get_format_string()] = format
611
529
 
612
530
    @classmethod
 
531
    def set_default_format(klass, format):
 
532
        klass._default_format = format
 
533
 
 
534
    @classmethod
613
535
    def unregister_format(klass, format):
614
536
        assert klass._formats[format.get_format_string()] is format
615
537
        del klass._formats[format.get_format_string()]
616
538
 
617
539
 
618
 
class BzrBranchFormat4(BzrBranchFormat):
 
540
class BzrBranchFormat4(BranchFormat):
619
541
    """Bzr branch format 4.
620
542
 
621
543
    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.
 
544
     - a revision-history file.
 
545
     - a branch-lock lock file [ to be shared with the bzrdir ]
627
546
    """
628
547
 
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.
 
548
    def initialize(self, a_bzrdir):
 
549
        """Create a branch of this format in a_bzrdir."""
 
550
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
551
        branch_transport = a_bzrdir.get_branch_transport(self)
 
552
        utf8_files = [('revision-history', ''),
 
553
                      ('branch-name', ''),
 
554
                      ]
 
555
        control_files = LockableFiles(branch_transport, 'branch-lock')
 
556
        control_files.lock_write()
 
557
        try:
 
558
            for file, content in utf8_files:
 
559
                control_files.put_utf8(file, content)
 
560
        finally:
 
561
            control_files.unlock()
 
562
        return self.open(a_bzrdir, _found=True)
 
563
 
 
564
    def __init__(self):
 
565
        super(BzrBranchFormat4, self).__init__()
 
566
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
567
 
 
568
    def open(self, a_bzrdir, _found=False):
 
569
        """Return the branch object for a_bzrdir
 
570
 
 
571
        _found is a private parameter, do not use it. It is used to indicate
 
572
               if format probing has already be done.
643
573
        """
644
 
        return False
645
 
 
646
 
 
647
 
class BzrBranchFormat5(BzrBranchFormat):
 
574
        if not _found:
 
575
            # we are being called directly and must probe.
 
576
            raise NotImplementedError
 
577
        return BzrBranch(_format=self,
 
578
                         _control_files=a_bzrdir._control_files,
 
579
                         a_bzrdir=a_bzrdir,
 
580
                         _repository=a_bzrdir.open_repository())
 
581
 
 
582
 
 
583
class BzrBranchFormat5(BranchFormat):
648
584
    """Bzr branch format 5.
649
585
 
650
586
    This format has:
651
 
     - weaves for file texts and inventory
652
 
     - flat stores
653
 
     - TextStores for revisions and signatures.
 
587
     - a revision-history file.
 
588
     - a format string
 
589
     - a lock file.
 
590
     - works with shared repositories.
654
591
    """
655
592
 
656
593
    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.
 
594
        """See BranchFormat.get_format_string()."""
 
595
        return "Bazaar-NG branch format 5\n"
 
596
        
 
597
    def initialize(self, a_bzrdir):
 
598
        """Create a branch of this format in a_bzrdir."""
 
599
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
600
        branch_transport = a_bzrdir.get_branch_transport(self)
 
601
 
 
602
        utf8_files = [('revision-history', ''),
 
603
                      ('branch-name', ''),
 
604
                      ]
 
605
        lock_file = 'lock'
 
606
        branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
 
607
        control_files = LockableFiles(branch_transport, 'lock')
 
608
        control_files.lock_write()
 
609
        control_files.put_utf8('format', self.get_format_string())
 
610
        try:
 
611
            for file, content in utf8_files:
 
612
                control_files.put_utf8(file, content)
 
613
        finally:
 
614
            control_files.unlock()
 
615
        return self.open(a_bzrdir, _found=True, )
 
616
 
 
617
    def __init__(self):
 
618
        super(BzrBranchFormat5, self).__init__()
 
619
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
620
 
 
621
    def open(self, a_bzrdir, _found=False):
 
622
        """Return the branch object for a_bzrdir
 
623
 
 
624
        _found is a private parameter, do not use it. It is used to indicate
 
625
               if format probing has already be done.
 
626
        """
 
627
        if not _found:
 
628
            format = BranchFormat.find_format(a_bzrdir)
 
629
            assert format.__class__ == self.__class__
 
630
        transport = a_bzrdir.get_branch_transport(None)
 
631
        control_files = LockableFiles(transport, 'lock')
 
632
        return BzrBranch(_format=self,
 
633
                         _control_files=control_files,
 
634
                         a_bzrdir=a_bzrdir,
 
635
                         _repository=a_bzrdir.find_repository())
 
636
 
 
637
 
 
638
class BranchReferenceFormat(BranchFormat):
 
639
    """Bzr branch reference format.
 
640
 
 
641
    Branch references are used in implementing checkouts, they
 
642
    act as an alias to the real branch which is at some other url.
663
643
 
664
644
    This format has:
665
 
     - weaves for file texts and inventory
666
 
     - hash subdirectory based stores.
667
 
     - TextStores for revisions and signatures.
 
645
     - A location file
 
646
     - a format string
668
647
    """
669
648
 
670
649
    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
 
 
 
650
        """See BranchFormat.get_format_string()."""
 
651
        return "Bazaar-NG Branch Reference Format 1\n"
 
652
        
 
653
    def initialize(self, a_bzrdir, target_branch=None):
 
654
        """Create a branch of this format in a_bzrdir."""
 
655
        if target_branch is None:
 
656
            # this format does not implement branch itself, thus the implicit
 
657
            # creation contract must see it as uninitializable
 
658
            raise errors.UninitializableFormat(self)
 
659
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
660
        branch_transport = a_bzrdir.get_branch_transport(self)
 
661
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
662
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
663
        branch_transport.put('format', StringIO(self.get_format_string()))
 
664
        return self.open(a_bzrdir, _found=True)
 
665
 
 
666
    def __init__(self):
 
667
        super(BranchReferenceFormat, self).__init__()
 
668
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
669
 
 
670
    def _make_reference_clone_function(format, a_branch):
 
671
        """Create a clone() routine for a branch dynamically."""
 
672
        def clone(to_bzrdir, revision_id=None):
 
673
            """See Branch.clone()."""
 
674
            return format.initialize(to_bzrdir, a_branch)
 
675
            # cannot obey revision_id limits when cloning a reference ...
 
676
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
677
            # emit some sort of warning/error to the caller ?!
 
678
        return clone
 
679
 
 
680
    def open(self, a_bzrdir, _found=False):
 
681
        """Return the branch that the branch reference in a_bzrdir points at.
 
682
 
 
683
        _found is a private parameter, do not use it. It is used to indicate
 
684
               if format probing has already be done.
 
685
        """
 
686
        if not _found:
 
687
            format = BranchFormat.find_format(a_bzrdir)
 
688
            assert format.__class__ == self.__class__
 
689
        transport = a_bzrdir.get_branch_transport(None)
 
690
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
691
        result = real_bzrdir.open_branch()
 
692
        # this changes the behaviour of result.clone to create a new reference
 
693
        # rather than a copy of the content of the branch.
 
694
        # I did not use a proxy object because that needs much more extensive
 
695
        # testing, and we are only changing one behaviour at the moment.
 
696
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
697
        # then this should be refactored to introduce a tested proxy branch
 
698
        # and a subclass of that for use in overriding clone() and ....
 
699
        # - RBC 20060210
 
700
        result.clone = self._make_reference_clone_function(result)
 
701
        return result
 
702
 
 
703
 
 
704
# formats which have no format string are not discoverable
 
705
# and not independently creatable, so are not registered.
 
706
__default_format = BzrBranchFormat5()
 
707
BranchFormat.register_format(__default_format)
 
708
BranchFormat.register_format(BranchReferenceFormat())
 
709
BranchFormat.set_default_format(__default_format)
 
710
_legacy_formats = [BzrBranchFormat4(),
 
711
                   ]
683
712
 
684
713
class BzrBranch(Branch):
685
714
    """A branch stored in the actual filesystem.
687
716
    Note that it's "local" in the context of the filesystem; it doesn't
688
717
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
689
718
    it's writable, and can be accessed via the normal filesystem API.
690
 
 
691
719
    """
692
720
    # We actually expect this class to be somewhat short-lived; part of its
693
721
    # purpose is to try to isolate what bits of the branch logic are tied to
700
728
    # This should match a prefix with a function which accepts
701
729
    REVISION_NAMESPACES = {}
702
730
 
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,
 
731
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
725
732
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
726
 
                 _control_files=None):
 
733
                 _control_files=None, a_bzrdir=None, _repository=None):
727
734
        """Create new branch object at a particular location.
728
735
 
729
736
        transport -- A Transport object, defining how to access files.
736
743
            version is not applied.  This is intended only for
737
744
            upgrade/recovery type use; it's not guaranteed that
738
745
            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
746
        """
743
 
        assert isinstance(transport, Transport), \
744
 
            "%r is not a Transport" % transport
745
 
        self._transport = transport
 
747
        if a_bzrdir is None:
 
748
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
749
        else:
 
750
            self.bzrdir = a_bzrdir
 
751
        self._transport = self.bzrdir.transport.clone('..')
746
752
        self._base = self._transport.base
 
753
        self._format = _format
747
754
        if _control_files is None:
748
 
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
749
 
                                           'branch-lock')
 
755
            raise BzrBadParameterMissing('_control_files')
750
756
        self.control_files = _control_files
751
757
        if deprecated_passed(init):
752
758
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
761
767
        if deprecated_passed(relax_version_check):
762
768
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
763
769
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
764
 
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
770
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
765
771
                 "open() method.",
766
772
                 DeprecationWarning,
767
773
                 stacklevel=2)
768
774
            if (not relax_version_check
769
 
                and not self._branch_format.is_supported()):
 
775
                and not self._format.is_supported()):
770
776
                raise errors.UnsupportedFormatError(
771
777
                        'sorry, branch format %r not supported' % fmt,
772
778
                        ['use a different bzr version',
773
779
                         'or remove the .bzr directory'
774
780
                         ' 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)
 
781
        if deprecated_passed(transport):
 
782
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
783
                 "parameter is deprecated as of bzr 0.8. "
 
784
                 "Please use Branch.open, or bzrdir.open_branch().",
 
785
                 DeprecationWarning,
 
786
                 stacklevel=2)
 
787
        self.repository = _repository
782
788
 
783
789
    def __str__(self):
784
790
        return '%s(%r)' % (self.__class__.__name__, self.base)
836
842
        """Identify the branch format if needed.
837
843
 
838
844
        The format is stored as a reference to the format object in
839
 
        self._branch_format for code that needs to check it later.
 
845
        self._format for code that needs to check it later.
840
846
 
841
847
        The format parameter is either None or the branch format class
842
848
        used to open this branch.
 
849
 
 
850
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
843
851
        """
844
852
        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)
 
853
            format = BzrBranchFormat.find_format(self.bzrdir)
 
854
        self._format = format
 
855
        mutter("got branch format %s", self._format)
848
856
 
849
857
    @needs_read_lock
850
858
    def get_root_id(self):
933
941
    def update_revisions(self, other, stop_revision=None):
934
942
        """See Branch.update_revisions."""
935
943
        from bzrlib.fetch import greedy_fetch
 
944
 
936
945
        if stop_revision is None:
937
946
            stop_revision = other.last_revision()
938
947
        ### Should this be checking is_ancestor instead of revision_history?
965
974
        
966
975
    def basis_tree(self):
967
976
        """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())
 
977
        return self.repository.revision_tree(self.last_revision())
979
978
 
 
979
    @deprecated_method(zero_eight)
980
980
    def working_tree(self):
981
 
        """See Branch.working_tree."""
 
981
        """Create a Working tree object for this branch."""
982
982
        from bzrlib.workingtree import WorkingTree
983
983
        from bzrlib.transport.local import LocalTransport
984
984
        if (self.base.find('://') != -1 or 
985
985
            not isinstance(self._transport, LocalTransport)):
986
986
            raise NoWorkingTree(self.base)
987
 
        return WorkingTree(self.base, branch=self)
 
987
        return self.bzrdir.open_workingtree()
988
988
 
989
989
    @needs_write_lock
990
 
    def pull(self, source, overwrite=False):
 
990
    def pull(self, source, overwrite=False, stop_revision=None):
991
991
        """See Branch.pull."""
992
992
        source.lock_read()
993
993
        try:
994
994
            old_count = len(self.revision_history())
995
995
            try:
996
 
                self.update_revisions(source)
 
996
                self.update_revisions(source,stop_revision)
997
997
            except DivergedBranches:
998
998
                if not overwrite:
999
999
                    raise
1060
1060
        history = self._get_truncated_history(revision)
1061
1061
        if not bzrlib.osutils.lexists(to_location):
1062
1062
            os.mkdir(to_location)
1063
 
        branch_to = Branch.initialize(to_location)
 
1063
        bzrdir_to = self.bzrdir._format.initialize(to_location)
 
1064
        self.repository.clone(bzrdir_to)
 
1065
        branch_to = bzrdir_to.create_branch()
1064
1066
        mutter("copy branch from %s to %s", self, branch_to)
1065
1067
 
1066
 
        self.repository.copy(branch_to.repository)
1067
 
        
1068
 
        # must be done *after* history is copied across
1069
1068
        # FIXME duplicate code with base .clone().
1070
1069
        # .. would template method be useful here?  RBC 20051207
1071
1070
        branch_to.set_parent(self.base)
1072
1071
        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())
 
1072
        WorkingTree.create(branch_to, branch_to.base)
1075
1073
        mutter("copied")
1076
1074
        return branch_to
1077
1075
 
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
1076
 
1186
1077
class BranchTestProviderAdapter(object):
1187
1078
    """A tool to generate a suite testing multiple branch formats at once.
1199
1090
    
1200
1091
    def adapt(self, test):
1201
1092
        result = TestSuite()
1202
 
        for format in self._formats:
 
1093
        for branch_format, bzrdir_format in self._formats:
1203
1094
            new_test = deepcopy(test)
1204
1095
            new_test.transport_server = self._transport_server
1205
1096
            new_test.transport_readonly_server = self._transport_readonly_server
1206
 
            new_test.branch_format = format
 
1097
            new_test.bzrdir_format = bzrdir_format
 
1098
            new_test.branch_format = branch_format
1207
1099
            def make_new_test_id():
1208
 
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1100
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1209
1101
                return lambda: new_id
1210
1102
            new_test.id = make_new_test_id()
1211
1103
            result.addTest(new_test)
1212
1104
        return result
1213
1105
 
1214
1106
 
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
1107
######################################################################
1280
1108
# predicates
1281
1109
 
1282
1110
 
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
 
1111
@deprecated_function(zero_eight)
 
1112
def ScratchBranch(*args, **kwargs):
 
1113
    """See bzrlib.bzrdir.ScratchDir."""
 
1114
    d = ScratchDir(*args, **kwargs)
 
1115
    return d.open_branch()
 
1116
 
 
1117
 
 
1118
@deprecated_function(zero_eight)
 
1119
def is_control_file(*args, **kwargs):
 
1120
    """See bzrlib.workingtree.is_control_file."""
 
1121
    return bzrlib.workingtree.is_control_file(*args, **kwargs)