~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Merge in bzr-dir phase 2.

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 = WorkingTree(br1.base, br1)
 
238
        >>> d2 = bzrdir.ScratchDir()
 
239
        >>> br2 = d2.open_branch()
 
240
        >>> wt2 = WorkingTree(br2.base, br2)
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
303
    def pull(self, source, overwrite=False, stop_revision=None):
338
304
        raise NotImplementedError('pull is abstract')
339
305
 
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
 
        # 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)
544
525
            file_mode = None
545
526
        return dir_mode, file_mode
546
527
 
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)
 
528
    def initialize(self, a_bzrdir):
 
529
        """Create a branch of this format in a_bzrdir."""
 
530
        raise NotImplementedError(self.initialized)
594
531
 
595
532
    def is_supported(self):
596
533
        """Is this format supported?
601
538
        """
602
539
        return True
603
540
 
604
 
    def open(self, transport):
605
 
        """Fill out the data in branch for the branch at url."""
606
 
        return BzrBranch(transport, _format=self)
 
541
    def open(self, a_bzrdir, _found=False):
 
542
        """Return the branch object for a_bzrdir
 
543
 
 
544
        _found is a private parameter, do not use it. It is used to indicate
 
545
               if format probing has already be done.
 
546
        """
 
547
        raise NotImplementedError(self.open)
607
548
 
608
549
    @classmethod
609
550
    def register_format(klass, format):
610
551
        klass._formats[format.get_format_string()] = format
611
552
 
612
553
    @classmethod
 
554
    def set_default_format(klass, format):
 
555
        klass._default_format = format
 
556
 
 
557
    @classmethod
613
558
    def unregister_format(klass, format):
614
559
        assert klass._formats[format.get_format_string()] is format
615
560
        del klass._formats[format.get_format_string()]
616
561
 
617
562
 
618
 
class BzrBranchFormat4(BzrBranchFormat):
 
563
class BzrBranchFormat4(BranchFormat):
619
564
    """Bzr branch format 4.
620
565
 
621
566
    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.
 
567
     - a revision-history file.
 
568
     - a branch-lock lock file [ to be shared with the bzrdir ]
627
569
    """
628
570
 
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.
 
571
    def initialize(self, a_bzrdir):
 
572
        """Create a branch of this format in a_bzrdir."""
 
573
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
574
        branch_transport = a_bzrdir.get_branch_transport(self)
 
575
        utf8_files = [('revision-history', ''),
 
576
                      ('branch-name', ''),
 
577
                      ]
 
578
        control_files = LockableFiles(branch_transport, 'branch-lock')
 
579
        control_files.lock_write()
 
580
        try:
 
581
            for file, content in utf8_files:
 
582
                control_files.put_utf8(file, content)
 
583
        finally:
 
584
            control_files.unlock()
 
585
        return self.open(a_bzrdir, _found=True)
 
586
 
 
587
    def __init__(self):
 
588
        super(BzrBranchFormat4, self).__init__()
 
589
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
590
 
 
591
    def open(self, a_bzrdir, _found=False):
 
592
        """Return the branch object for a_bzrdir
 
593
 
 
594
        _found is a private parameter, do not use it. It is used to indicate
 
595
               if format probing has already be done.
643
596
        """
644
 
        return False
645
 
 
646
 
 
647
 
class BzrBranchFormat5(BzrBranchFormat):
 
597
        if not _found:
 
598
            # we are being called directly and must probe.
 
599
            raise NotImplementedError
 
600
        transport = a_bzrdir.get_branch_transport(self)
 
601
        control_files = LockableFiles(transport, 'branch-lock')
 
602
        return BzrBranch(_format=self,
 
603
                         _control_files=control_files,
 
604
                         a_bzrdir=a_bzrdir)
 
605
 
 
606
 
 
607
class BzrBranchFormat5(BranchFormat):
648
608
    """Bzr branch format 5.
649
609
 
650
610
    This format has:
651
 
     - weaves for file texts and inventory
652
 
     - flat stores
653
 
     - TextStores for revisions and signatures.
 
611
     - a revision-history file.
 
612
     - a format string
 
613
     - a lock lock file.
654
614
    """
655
615
 
656
616
    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.
 
617
        """See BranchFormat.get_format_string()."""
 
618
        return "Bazaar-NG branch format 5\n"
 
619
        
 
620
    def initialize(self, a_bzrdir):
 
621
        """Create a branch of this format in a_bzrdir."""
 
622
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
623
        branch_transport = a_bzrdir.get_branch_transport(self)
 
624
 
 
625
        utf8_files = [('revision-history', ''),
 
626
                      ('branch-name', ''),
 
627
                      ]
 
628
        lock_file = 'lock'
 
629
        branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
 
630
        control_files = LockableFiles(branch_transport, 'lock')
 
631
        control_files.lock_write()
 
632
        control_files.put_utf8('format', self.get_format_string())
 
633
        try:
 
634
            for file, content in utf8_files:
 
635
                control_files.put_utf8(file, content)
 
636
        finally:
 
637
            control_files.unlock()
 
638
        return self.open(a_bzrdir, _found=True, )
 
639
 
 
640
    def __init__(self):
 
641
        super(BzrBranchFormat5, self).__init__()
 
642
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
643
 
 
644
    def open(self, a_bzrdir, _found=False):
 
645
        """Return the branch object for a_bzrdir
 
646
 
 
647
        _found is a private parameter, do not use it. It is used to indicate
 
648
               if format probing has already be done.
 
649
        """
 
650
        if not _found:
 
651
            format = BranchFormat.find_format(a_bzrdir)
 
652
            assert format.__class__ == self.__class__
 
653
        transport = a_bzrdir.get_branch_transport(None)
 
654
        control_files = LockableFiles(transport, 'lock')
 
655
        return BzrBranch(_format=self,
 
656
                         _control_files=control_files,
 
657
                         a_bzrdir=a_bzrdir)
 
658
 
 
659
 
 
660
class BranchReferenceFormat(BranchFormat):
 
661
    """Bzr branch reference format.
 
662
 
 
663
    Branch references are used in implementing checkouts, they
 
664
    act as an alias to the real branch which is at some other url.
663
665
 
664
666
    This format has:
665
 
     - weaves for file texts and inventory
666
 
     - hash subdirectory based stores.
667
 
     - TextStores for revisions and signatures.
 
667
     - A location file
 
668
     - a format string
668
669
    """
669
670
 
670
671
    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
 
 
 
672
        """See BranchFormat.get_format_string()."""
 
673
        return "Bazaar-NG Branch Reference Format 1\n"
 
674
        
 
675
    def initialize(self, a_bzrdir, target_branch=None):
 
676
        """Create a branch of this format in a_bzrdir."""
 
677
        if target_branch is None:
 
678
            # this format does not implement branch itself, thus the implicit
 
679
            # creation contract must see it as uninitializable
 
680
            raise errors.UninitializableFormat(self)
 
681
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
682
        branch_transport = a_bzrdir.get_branch_transport(self)
 
683
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
684
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
685
        branch_transport.put('format', StringIO(self.get_format_string()))
 
686
        return self.open(a_bzrdir, _found=True)
 
687
 
 
688
    def __init__(self):
 
689
        super(BranchReferenceFormat, self).__init__()
 
690
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
691
 
 
692
    def _make_reference_clone_function(format, a_branch):
 
693
        """Create a clone() routine for a branch dynamically."""
 
694
        def clone(to_bzrdir, revision_id=None):
 
695
            """See Branch.clone()."""
 
696
            return format.initialize(to_bzrdir, a_branch)
 
697
            # cannot obey revision_id limits when cloning a reference ...
 
698
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
699
            # emit some sort of warning/error to the caller ?!
 
700
        return clone
 
701
 
 
702
    def open(self, a_bzrdir, _found=False):
 
703
        """Return the branch that the branch reference in a_bzrdir points at.
 
704
 
 
705
        _found is a private parameter, do not use it. It is used to indicate
 
706
               if format probing has already be done.
 
707
        """
 
708
        if not _found:
 
709
            format = BranchFormat.find_format(a_bzrdir)
 
710
            assert format.__class__ == self.__class__
 
711
        transport = a_bzrdir.get_branch_transport(None)
 
712
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
713
        result = real_bzrdir.open_branch()
 
714
        # this changes the behaviour of result.clone to create a new reference
 
715
        # rather than a copy of the content of the branch.
 
716
        # I did not use a proxy object because that needs much more extensive
 
717
        # testing, and we are only changing one behaviour at the moment.
 
718
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
719
        # then this should be refactored to introduce a tested proxy branch
 
720
        # and a subclass of that for use in overriding clone() and ....
 
721
        # - RBC 20060210
 
722
        result.clone = self._make_reference_clone_function(result)
 
723
        return result
 
724
 
 
725
 
 
726
# formats which have no format string are not discoverable
 
727
# and not independently creatable, so are not registered.
 
728
__default_format = BzrBranchFormat5()
 
729
BranchFormat.register_format(__default_format)
 
730
BranchFormat.register_format(BranchReferenceFormat())
 
731
BranchFormat.set_default_format(__default_format)
 
732
_legacy_formats = [BzrBranchFormat4(),
 
733
                   ]
683
734
 
684
735
class BzrBranch(Branch):
685
736
    """A branch stored in the actual filesystem.
687
738
    Note that it's "local" in the context of the filesystem; it doesn't
688
739
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
689
740
    it's writable, and can be accessed via the normal filesystem API.
690
 
 
691
741
    """
692
742
    # We actually expect this class to be somewhat short-lived; part of its
693
743
    # purpose is to try to isolate what bits of the branch logic are tied to
700
750
    # This should match a prefix with a function which accepts
701
751
    REVISION_NAMESPACES = {}
702
752
 
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,
 
753
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
725
754
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
726
 
                 _control_files=None):
 
755
                 _control_files=None, a_bzrdir=None):
727
756
        """Create new branch object at a particular location.
728
757
 
729
758
        transport -- A Transport object, defining how to access files.
736
765
            version is not applied.  This is intended only for
737
766
            upgrade/recovery type use; it's not guaranteed that
738
767
            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
768
        """
743
 
        assert isinstance(transport, Transport), \
744
 
            "%r is not a Transport" % transport
745
 
        self._transport = transport
 
769
        if a_bzrdir is None:
 
770
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
771
        else:
 
772
            self.bzrdir = a_bzrdir
 
773
        self._transport = self.bzrdir.transport.clone('..')
746
774
        self._base = self._transport.base
 
775
        self._format = _format
747
776
        if _control_files is None:
748
 
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
749
 
                                           'branch-lock')
 
777
            raise BzrBadParameterMissing('_control_files')
750
778
        self.control_files = _control_files
751
779
        if deprecated_passed(init):
752
780
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
761
789
        if deprecated_passed(relax_version_check):
762
790
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
763
791
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
764
 
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
792
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
765
793
                 "open() method.",
766
794
                 DeprecationWarning,
767
795
                 stacklevel=2)
768
796
            if (not relax_version_check
769
 
                and not self._branch_format.is_supported()):
 
797
                and not self._format.is_supported()):
770
798
                raise errors.UnsupportedFormatError(
771
799
                        'sorry, branch format %r not supported' % fmt,
772
800
                        ['use a different bzr version',
773
801
                         'or remove the .bzr directory'
774
802
                         ' 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)
 
803
        if deprecated_passed(transport):
 
804
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
805
                 "parameter is deprecated as of bzr 0.8. "
 
806
                 "Please use Branch.open, or bzrdir.open_branch().",
 
807
                 DeprecationWarning,
 
808
                 stacklevel=2)
 
809
        # TODO change this to search upwards if needed.
 
810
        self.repository = self.bzrdir.open_repository()
782
811
 
783
812
    def __str__(self):
784
813
        return '%s(%r)' % (self.__class__.__name__, self.base)
836
865
        """Identify the branch format if needed.
837
866
 
838
867
        The format is stored as a reference to the format object in
839
 
        self._branch_format for code that needs to check it later.
 
868
        self._format for code that needs to check it later.
840
869
 
841
870
        The format parameter is either None or the branch format class
842
871
        used to open this branch.
 
872
 
 
873
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
843
874
        """
844
875
        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)
 
876
            format = BzrBranchFormat.find_format(self.bzrdir)
 
877
        self._format = format
 
878
        mutter("got branch format %s", self._format)
848
879
 
849
880
    @needs_read_lock
850
881
    def get_root_id(self):
966
997
        
967
998
    def basis_tree(self):
968
999
        """See Branch.basis_tree."""
969
 
        try:
970
 
            revision_id = self.revision_history()[-1]
971
 
            # FIXME: This is an abstraction violation, the basis tree 
972
 
            # here as defined is on the working tree, the method should
973
 
            # be too. The basis tree for a branch can be different than
974
 
            # that for a working tree. RBC 20051207
975
 
            xml = self.working_tree().read_basis_inventory(revision_id)
976
 
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
977
 
            return RevisionTree(self.repository, inv, revision_id)
978
 
        except (IndexError, NoSuchFile, NoWorkingTree), e:
979
 
            return self.repository.revision_tree(self.last_revision())
 
1000
        return self.repository.revision_tree(self.last_revision())
980
1001
 
 
1002
    @deprecated_method(zero_eight)
981
1003
    def working_tree(self):
982
 
        """See Branch.working_tree."""
 
1004
        """Create a Working tree object for this branch."""
983
1005
        from bzrlib.workingtree import WorkingTree
984
1006
        from bzrlib.transport.local import LocalTransport
985
1007
        if (self.base.find('://') != -1 or 
1061
1083
        history = self._get_truncated_history(revision)
1062
1084
        if not bzrlib.osutils.lexists(to_location):
1063
1085
            os.mkdir(to_location)
1064
 
        branch_to = Branch.initialize(to_location)
 
1086
        bzrdir_to = self.bzrdir._format.initialize(to_location)
 
1087
        self.repository.clone(bzrdir_to)
 
1088
        branch_to = bzrdir_to.create_branch()
1065
1089
        mutter("copy branch from %s to %s", self, branch_to)
1066
1090
 
1067
 
        self.repository.copy(branch_to.repository)
1068
 
        
1069
 
        # must be done *after* history is copied across
1070
1091
        # FIXME duplicate code with base .clone().
1071
1092
        # .. would template method be useful here?  RBC 20051207
1072
1093
        branch_to.set_parent(self.base)
1073
1094
        branch_to.append_revision(*history)
1074
 
        # FIXME: this should be in workingtree.clone
1075
 
        WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
 
1095
        WorkingTree.create(branch_to, branch_to.base)
1076
1096
        mutter("copied")
1077
1097
        return branch_to
1078
1098
 
1079
 
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
1080
 
        # FIXME: clone via create and fetch is probably faster when versioned
1081
 
        # file comes in.
1082
 
        if to_branch_type is None:
1083
 
            to_branch_type = BzrBranch
1084
 
 
1085
 
        if to_branch_type == BzrBranch \
1086
 
            and self.repository.weave_store.listable() \
1087
 
            and self.repository.revision_store.listable():
1088
 
            return self._clone_weave(to_location, revision, basis_branch)
1089
 
 
1090
 
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1091
 
 
1092
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
1093
 
        """Find file_id(s) which are involved in the changes between revisions.
1094
 
 
1095
 
        This determines the set of revisions which are involved, and then
1096
 
        finds all file ids affected by those revisions.
1097
 
        """
1098
 
        # TODO: jam 20060119 This code assumes that w.inclusions will
1099
 
        #       always be correct. But because of the presence of ghosts
1100
 
        #       it is possible to be wrong.
1101
 
        #       One specific example from Robert Collins:
1102
 
        #       Two branches, with revisions ABC, and AD
1103
 
        #       C is a ghost merge of D.
1104
 
        #       Inclusions doesn't recognize D as an ancestor.
1105
 
        #       If D is ever merged in the future, the weave
1106
 
        #       won't be fixed, because AD never saw revision C
1107
 
        #       to cause a conflict which would force a reweave.
1108
 
        w = self.repository.get_inventory_weave()
1109
 
        from_set = set(w.inclusions([w.lookup(from_revid)]))
1110
 
        to_set = set(w.inclusions([w.lookup(to_revid)]))
1111
 
        included = to_set.difference(from_set)
1112
 
        changed = map(w.idx_to_name, included)
1113
 
        return self._fileid_involved_by_set(changed)
1114
 
 
1115
 
    def fileid_involved(self, last_revid=None):
1116
 
        """Find all file_ids modified in the ancestry of last_revid.
1117
 
 
1118
 
        :param last_revid: If None, last_revision() will be used.
1119
 
        """
1120
 
        w = self.repository.get_inventory_weave()
1121
 
        if not last_revid:
1122
 
            changed = set(w._names)
1123
 
        else:
1124
 
            included = w.inclusions([w.lookup(last_revid)])
1125
 
            changed = map(w.idx_to_name, included)
1126
 
        return self._fileid_involved_by_set(changed)
1127
 
 
1128
 
    def fileid_involved_by_set(self, changes):
1129
 
        """Find all file_ids modified by the set of revisions passed in.
1130
 
 
1131
 
        :param changes: A set() of revision ids
1132
 
        """
1133
 
        # TODO: jam 20060119 This line does *nothing*, remove it.
1134
 
        #       or better yet, change _fileid_involved_by_set so
1135
 
        #       that it takes the inventory weave, rather than
1136
 
        #       pulling it out by itself.
1137
 
        w = self.repository.get_inventory_weave()
1138
 
        return self._fileid_involved_by_set(changes)
1139
 
 
1140
 
    def _fileid_involved_by_set(self, changes):
1141
 
        """Find the set of file-ids affected by the set of revisions.
1142
 
 
1143
 
        :param changes: A set() of revision ids.
1144
 
        :return: A set() of file ids.
1145
 
        
1146
 
        This peaks at the Weave, interpreting each line, looking to
1147
 
        see if it mentions one of the revisions. And if so, includes
1148
 
        the file id mentioned.
1149
 
        This expects both the Weave format, and the serialization
1150
 
        to have a single line per file/directory, and to have
1151
 
        fileid="" and revision="" on that line.
1152
 
        """
1153
 
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
1154
 
                isinstance(self._branch_format, BzrBranchFormat6)), \
1155
 
            "fileid_involved only supported for branches which store inventory as xml"
1156
 
 
1157
 
        w = self.repository.get_inventory_weave()
1158
 
        file_ids = set()
1159
 
        for line in w._weave:
1160
 
 
1161
 
            # it is ugly, but it is due to the weave structure
1162
 
            if not isinstance(line, basestring): continue
1163
 
 
1164
 
            start = line.find('file_id="')+9
1165
 
            if start < 9: continue
1166
 
            end = line.find('"', start)
1167
 
            assert end>= 0
1168
 
            file_id = xml.sax.saxutils.unescape(line[start:end])
1169
 
 
1170
 
            # check if file_id is already present
1171
 
            if file_id in file_ids: continue
1172
 
 
1173
 
            start = line.find('revision="')+10
1174
 
            if start < 10: continue
1175
 
            end = line.find('"', start)
1176
 
            assert end>= 0
1177
 
            revision_id = xml.sax.saxutils.unescape(line[start:end])
1178
 
 
1179
 
            if revision_id in changes:
1180
 
                file_ids.add(file_id)
1181
 
 
1182
 
        return file_ids
1183
 
 
1184
 
 
1185
 
Branch.set_default_initializer(BzrBranch._initialize)
1186
 
 
1187
1099
 
1188
1100
class BranchTestProviderAdapter(object):
1189
1101
    """A tool to generate a suite testing multiple branch formats at once.
1201
1113
    
1202
1114
    def adapt(self, test):
1203
1115
        result = TestSuite()
1204
 
        for format in self._formats:
 
1116
        for branch_format, bzrdir_format in self._formats:
1205
1117
            new_test = deepcopy(test)
1206
1118
            new_test.transport_server = self._transport_server
1207
1119
            new_test.transport_readonly_server = self._transport_readonly_server
1208
 
            new_test.branch_format = format
 
1120
            new_test.bzrdir_format = bzrdir_format
 
1121
            new_test.branch_format = branch_format
1209
1122
            def make_new_test_id():
1210
 
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1123
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1211
1124
                return lambda: new_id
1212
1125
            new_test.id = make_new_test_id()
1213
1126
            result.addTest(new_test)
1214
1127
        return result
1215
1128
 
1216
1129
 
1217
 
class ScratchBranch(BzrBranch):
1218
 
    """Special test class: a branch that cleans up after itself.
1219
 
 
1220
 
    >>> b = ScratchBranch()
1221
 
    >>> isdir(b.base)
1222
 
    True
1223
 
    >>> bd = b.base
1224
 
    >>> b._transport.__del__()
1225
 
    >>> isdir(bd)
1226
 
    False
1227
 
    """
1228
 
 
1229
 
    def __init__(self, files=[], dirs=[], transport=None):
1230
 
        """Make a test branch.
1231
 
 
1232
 
        This creates a temporary directory and runs init-tree in it.
1233
 
 
1234
 
        If any files are listed, they are created in the working copy.
1235
 
        """
1236
 
        if transport is None:
1237
 
            transport = bzrlib.transport.local.ScratchTransport()
1238
 
            # local import for scope restriction
1239
 
            from bzrlib.workingtree import WorkingTree
1240
 
            WorkingTree.create_standalone(transport.base)
1241
 
            super(ScratchBranch, self).__init__(transport)
1242
 
        else:
1243
 
            super(ScratchBranch, self).__init__(transport)
1244
 
 
1245
 
        # BzrBranch creates a clone to .bzr and then forgets about the
1246
 
        # original transport. A ScratchTransport() deletes itself and
1247
 
        # everything underneath it when it goes away, so we need to
1248
 
        # grab a local copy to prevent that from happening
1249
 
        self._transport = transport
1250
 
 
1251
 
        for d in dirs:
1252
 
            self._transport.mkdir(d)
1253
 
            
1254
 
        for f in files:
1255
 
            self._transport.put(f, 'content of %s' % f)
1256
 
 
1257
 
    def clone(self):
1258
 
        """
1259
 
        >>> orig = ScratchBranch(files=["file1", "file2"])
1260
 
        >>> os.listdir(orig.base)
1261
 
        [u'.bzr', u'file1', u'file2']
1262
 
        >>> clone = orig.clone()
1263
 
        >>> if os.name != 'nt':
1264
 
        ...   os.path.samefile(orig.base, clone.base)
1265
 
        ... else:
1266
 
        ...   orig.base == clone.base
1267
 
        ...
1268
 
        False
1269
 
        >>> os.listdir(clone.base)
1270
 
        [u'.bzr', u'file1', u'file2']
1271
 
        """
1272
 
        from shutil import copytree
1273
 
        from bzrlib.osutils import mkdtemp
1274
 
        base = mkdtemp()
1275
 
        os.rmdir(base)
1276
 
        copytree(self.base, base, symlinks=True)
1277
 
        return ScratchBranch(
1278
 
            transport=bzrlib.transport.local.ScratchTransport(base))
1279
 
    
1280
 
 
1281
1130
######################################################################
1282
1131
# predicates
1283
1132
 
1284
1133
 
1285
 
def is_control_file(filename):
1286
 
    ## FIXME: better check
1287
 
    filename = normpath(filename)
1288
 
    while filename != '':
1289
 
        head, tail = os.path.split(filename)
1290
 
        ## mutter('check %r for control file' % ((head, tail),))
1291
 
        if tail == bzrlib.BZRDIR:
1292
 
            return True
1293
 
        if filename == head:
1294
 
            break
1295
 
        filename = head
1296
 
    return False
 
1134
@deprecated_function(zero_eight)
 
1135
def ScratchBranch(*args, **kwargs):
 
1136
    """See bzrlib.bzrdir.ScratchDir."""
 
1137
    d = ScratchDir(*args, **kwargs)
 
1138
    return d.open_branch()
 
1139
 
 
1140
 
 
1141
@deprecated_function(zero_eight)
 
1142
def is_control_file(*args, **kwargs):
 
1143
    """See bzrlib.workingtree.is_control_file."""
 
1144
    return bzrlib.workingtree.is_control_file(*args, **kwargs)