~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Basic BzrDir support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
23
23
import sys
24
24
from unittest import TestSuite
25
25
from warnings import warn
26
 
try:
27
 
    import xml.sax.saxutils
28
 
except ImportError:
29
 
    raise ImportError("We were unable to import 'xml.sax.saxutils',"
30
 
                      " most likely you have an xml.pyc or xml.pyo file"
31
 
                      " lying around in your bzrlib directory."
32
 
                      " Please remove it.")
 
26
import xml.sax.saxutils
33
27
 
34
28
 
35
29
import bzrlib
36
 
import bzrlib.bzrdir as bzrdir
37
30
from bzrlib.config import TreeConfig
38
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
32
from bzrlib.delta import compare_trees
40
33
import bzrlib.errors as errors
41
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
42
35
                           NoSuchRevision, HistoryMissing, NotBranchError,
43
 
                           DivergedBranches, LockError,
 
36
                           DivergedBranches, LockError, 
44
37
                           UninitializableFormat,
45
38
                           UnlistableStore,
46
39
                           UnlistableBranch, NoSuchFile, NotVersionedError,
94
87
    # - RBC 20060112
95
88
    base = None
96
89
 
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)
 
90
    _default_initializer = None
 
91
    """The default initializer for making new branches."""
108
92
 
109
93
    def __init__(self, *ignored, **ignored_too):
110
94
        raise NotImplementedError('The Branch class is abstract')
111
95
 
112
96
    @staticmethod
113
 
    @deprecated_method(zero_eight)
114
97
    def open_downlevel(base):
115
98
        """Open a branch which may be of an old format."""
116
99
        return Branch.open(base, _unsupported=True)
117
100
        
118
101
    @staticmethod
119
102
    def open(base, _unsupported=False):
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.
 
103
        """Open an existing branch, rooted at 'base' (url)
 
104
        
 
105
        _unsupported is a private parameter to the Branch class.
124
106
        """
125
 
        control = bzrdir.BzrDir.open(base, _unsupported)
126
 
        return control.open_branch(_unsupported)
 
107
        t = get_transport(base)
 
108
        mutter("trying to open %r with transport %r", base, t)
 
109
        format = BzrBranchFormat.find_format(t)
 
110
        if not _unsupported and not format.is_supported():
 
111
            # see open_downlevel to open legacy branches.
 
112
            raise errors.UnsupportedFormatError(
 
113
                    'sorry, branch format %s not supported' % format,
 
114
                    ['use a different bzr version',
 
115
                     'or remove the .bzr directory'
 
116
                     ' and "bzr init" again'])
 
117
        return format.open(t)
127
118
 
128
119
    @staticmethod
129
120
    def open_containing(url):
137
128
        format, UnknownFormatError or UnsupportedFormatError are raised.
138
129
        If there is one, it is returned, along with the unused portion of url.
139
130
        """
140
 
        control, relpath = bzrdir.BzrDir.open_containing(url)
141
 
        return control.open_branch(), relpath
 
131
        t = get_transport(url)
 
132
        # this gets the normalised url back. I.e. '.' -> the full path.
 
133
        url = t.base
 
134
        while True:
 
135
            try:
 
136
                format = BzrBranchFormat.find_format(t)
 
137
                return format.open(t), t.relpath(url)
 
138
            except NotBranchError, e:
 
139
                mutter('not a branch in: %r %s', t.base, e)
 
140
            new_t = t.clone('..')
 
141
            if new_t.base == t.base:
 
142
                # reached the root, whatever that may be
 
143
                raise NotBranchError(path=url)
 
144
            t = new_t
 
145
 
 
146
    @staticmethod
 
147
    def create(base):
 
148
        """Create a new Branch at the url 'bzr'.
 
149
        
 
150
        This will call the current default initializer with base
 
151
        as the only parameter.
 
152
        """
 
153
        return Branch._default_initializer(safe_unicode(base))
142
154
 
143
155
    @staticmethod
144
156
    @deprecated_function(zero_eight)
145
157
    def initialize(base):
146
158
        """Create a new working tree and branch, rooted at 'base' (url)
147
 
 
148
 
        NOTE: This will soon be deprecated in favour of creation
149
 
        through a BzrDir.
150
159
        """
151
 
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
 
160
        # imported here to prevent scope creep as this is going.
 
161
        from bzrlib.workingtree import WorkingTree
 
162
        return WorkingTree.create_standalone(safe_unicode(base)).branch
 
163
 
 
164
    @staticmethod
 
165
    def get_default_initializer():
 
166
        """Return the initializer being used for new branches."""
 
167
        return Branch._default_initializer
 
168
 
 
169
    @staticmethod
 
170
    def set_default_initializer(initializer):
 
171
        """Set the initializer to be used for new branches."""
 
172
        Branch._default_initializer = staticmethod(initializer)
152
173
 
153
174
    def setup_caching(self, cache_root):
154
175
        """Subclasses that care about caching should override this, and set
169
190
 
170
191
    nick = property(_get_nick, _set_nick)
171
192
        
 
193
    def push_stores(self, branch_to):
 
194
        """Copy the content of this branches store to branch_to."""
 
195
        raise NotImplementedError('push_stores is abstract')
 
196
 
172
197
    def lock_write(self):
173
198
        raise NotImplementedError('lock_write is abstract')
174
199
        
232
257
 
233
258
        >>> from bzrlib.workingtree import WorkingTree
234
259
        >>> bzrlib.trace.silent = True
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()
 
260
        >>> br1 = ScratchBranch()
 
261
        >>> wt1 = WorkingTree(br1.base, br1)
 
262
        ...
 
263
        >>> br2 = ScratchBranch()
 
264
        >>> wt2 = WorkingTree(br2.base, br2)
 
265
        ...
241
266
        >>> br1.missing_revisions(br2)
242
267
        []
243
268
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
368
393
        """
369
394
        if revno < 1 or revno > self.revno():
370
395
            raise InvalidRevisionNumber(revno)
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:
 
396
        
 
397
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
398
        """Copy this branch into the existing directory to_location.
 
399
 
 
400
        Returns the newly created branch object.
 
401
 
 
402
        revision
 
403
            If not None, only revisions up to this point will be copied.
 
404
            The head of the new branch will be that revision.  Must be a
 
405
            revid or None.
 
406
    
 
407
        to_location -- The destination directory; must either exist and be 
 
408
            empty, or not exist, in which case it is created.
 
409
    
 
410
        basis_branch
 
411
            A local branch to copy revisions from, related to this branch. 
 
412
            This is used when branching from a remote (slow) branch, and we have
 
413
            a local branch that might contain some relevant revisions.
 
414
    
 
415
        to_branch_type
 
416
            Branch type of destination branch
 
417
        """
 
418
        from bzrlib.workingtree import WorkingTree
 
419
        assert isinstance(to_location, basestring)
 
420
        segments = to_location.split('/')
 
421
        if segments and segments[-1] not in ('', '.'):
 
422
            parent = '/'.join(segments[:-1])
 
423
            t = get_transport(parent)
449
424
            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):
 
425
                t.mkdir(segments[-1])
 
426
            except errors.FileExists:
 
427
                pass
 
428
        if to_branch_format is None:
 
429
            # use the default
 
430
            br_to = Branch.create(to_location)
 
431
        else:
 
432
            br_to = to_branch_format.initialize(to_location)
 
433
        mutter("copy branch from %s to %s", self, br_to)
 
434
        if basis_branch is not None:
 
435
            basis_branch.push_stores(br_to)
 
436
        if revision is None:
 
437
            revision = self.last_revision()
 
438
        br_to.update_revisions(self, stop_revision=revision)
 
439
        br_to.set_parent(self.base)
 
440
        mutter("copied")
 
441
        return br_to
 
442
 
 
443
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
444
        """ This function returns the file_id(s) involved in the
 
445
            changes between the from_revid revision and the to_revid
 
446
            revision
 
447
        """
 
448
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
449
 
 
450
    def fileid_involved(self, last_revid=None):
 
451
        """ This function returns the file_id(s) involved in the
 
452
            changes up to the revision last_revid
 
453
            If no parametr is passed, then all file_id[s] present in the
 
454
            repository are returned
 
455
        """
 
456
        raise NotImplementedError('fileid_involved is abstract')
 
457
 
 
458
    def fileid_involved_by_set(self, changes):
 
459
        """ This function returns the file_id(s) involved in the
 
460
            changes present in the set 'changes'
 
461
        """
 
462
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
463
 
 
464
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
465
        """ This function returns the file_id(s) involved in the
 
466
            changes between the from_revid revision and the to_revid
 
467
            revision
 
468
        """
 
469
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
470
 
 
471
    def fileid_involved(self, last_revid=None):
 
472
        """ This function returns the file_id(s) involved in the
 
473
            changes up to the revision last_revid
 
474
            If no parametr is passed, then all file_id[s] present in the
 
475
            repository are returned
 
476
        """
 
477
        raise NotImplementedError('fileid_involved is abstract')
 
478
 
 
479
    def fileid_involved_by_set(self, changes):
 
480
        """ This function returns the file_id(s) involved in the
 
481
            changes present in the set 'changes'
 
482
        """
 
483
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
484
 
 
485
class BzrBranchFormat(object):
461
486
    """An encapsulation of the initialization and open routines for a format.
462
487
 
463
488
    Formats provide three things:
475
500
    object will be created every time regardless.
476
501
    """
477
502
 
478
 
    _default_format = None
479
 
    """The default format used for new branches."""
480
 
 
481
503
    _formats = {}
482
504
    """The known formats."""
483
505
 
484
506
    @classmethod
485
 
    def find_format(klass, a_bzrdir):
486
 
        """Return the format for the branch object in a_bzrdir."""
 
507
    def find_format(klass, transport):
 
508
        """Return the format registered for URL."""
487
509
        try:
488
 
            transport = a_bzrdir.get_branch_transport(None)
489
 
            format_string = transport.get("format").read()
 
510
            format_string = transport.get(".bzr/branch-format").read()
490
511
            return klass._formats[format_string]
491
512
        except NoSuchFile:
492
513
            raise NotBranchError(path=transport.base)
493
514
        except KeyError:
494
515
            raise errors.UnknownFormatError(format_string)
495
516
 
496
 
    @classmethod
497
 
    def get_default_format(klass):
498
 
        """Return the current default format."""
499
 
        return klass._default_format
500
 
 
501
517
    def get_format_string(self):
502
518
        """Return the ASCII format string that identifies this format."""
503
519
        raise NotImplementedError(self.get_format_string)
525
541
            file_mode = None
526
542
        return dir_mode, file_mode
527
543
 
528
 
    def initialize(self, a_bzrdir):
529
 
        """Create a branch of this format in a_bzrdir."""
530
 
        raise NotImplementedError(self.initialized)
 
544
    def initialize(self, url):
 
545
        """Create a branch of this format at url and return an open branch."""
 
546
        t = get_transport(url)
 
547
        from bzrlib.weavefile import write_weave_v5
 
548
        from bzrlib.weave import Weave
 
549
        
 
550
        # Create an empty weave
 
551
        sio = StringIO()
 
552
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
553
        empty_weave = sio.getvalue()
 
554
 
 
555
        # Since we don't have a .bzr directory, inherit the
 
556
        # mode from the root directory
 
557
        temp_control = LockableFiles(t, '')
 
558
        temp_control._transport.mkdir('.bzr',
 
559
                                      mode=temp_control._dir_mode)
 
560
        file_mode = temp_control._file_mode
 
561
        del temp_control
 
562
        mutter('created control directory in ' + t.base)
 
563
        control = t.clone('.bzr')
 
564
        dirs = ['revision-store', 'weaves']
 
565
        lock_file = 'branch-lock'
 
566
        utf8_files = [('README', 
 
567
                       "This is a Bazaar-NG control directory.\n"
 
568
                       "Do not change any files in this directory.\n"),
 
569
                      ('branch-format', self.get_format_string()),
 
570
                      ('revision-history', ''),
 
571
                      ('branch-name', ''),
 
572
                      ]
 
573
        files = [('inventory.weave', StringIO(empty_weave)), 
 
574
                 ]
 
575
        
 
576
        # FIXME: RBC 20060125 dont peek under the covers
 
577
        # NB: no need to escape relative paths that are url safe.
 
578
        control.put(lock_file, StringIO(), mode=file_mode)
 
579
        control_files = LockableFiles(control, lock_file)
 
580
        control_files.lock_write()
 
581
        control_files._transport.mkdir_multi(dirs,
 
582
                mode=control_files._dir_mode)
 
583
        try:
 
584
            for file, content in utf8_files:
 
585
                control_files.put_utf8(file, content)
 
586
            for file, content in files:
 
587
                control_files.put(file, content)
 
588
        finally:
 
589
            control_files.unlock()
 
590
        return BzrBranch(t, _format=self, _control_files=control_files)
531
591
 
532
592
    def is_supported(self):
533
593
        """Is this format supported?
538
598
        """
539
599
        return True
540
600
 
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)
 
601
    def open(self, transport):
 
602
        """Fill out the data in branch for the branch at url."""
 
603
        return BzrBranch(transport, _format=self)
548
604
 
549
605
    @classmethod
550
606
    def register_format(klass, format):
551
607
        klass._formats[format.get_format_string()] = format
552
608
 
553
609
    @classmethod
554
 
    def set_default_format(klass, format):
555
 
        klass._default_format = format
556
 
 
557
 
    @classmethod
558
610
    def unregister_format(klass, format):
559
611
        assert klass._formats[format.get_format_string()] is format
560
612
        del klass._formats[format.get_format_string()]
561
613
 
562
614
 
563
 
class BzrBranchFormat4(BranchFormat):
 
615
class BzrBranchFormat4(BzrBranchFormat):
564
616
    """Bzr branch format 4.
565
617
 
566
618
    This format has:
567
 
     - a revision-history file.
568
 
     - a branch-lock lock file [ to be shared with the bzrdir ]
 
619
     - flat stores
 
620
     - TextStores for texts, inventories,revisions.
 
621
 
 
622
    This format is deprecated: it indexes texts using a text it which is
 
623
    removed in format 5; write support for this format has been removed.
569
624
    """
570
625
 
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.
 
626
    def get_format_string(self):
 
627
        """See BzrBranchFormat.get_format_string()."""
 
628
        return BZR_BRANCH_FORMAT_4
 
629
 
 
630
    def initialize(self, url):
 
631
        """Format 4 branches cannot be created."""
 
632
        raise UninitializableFormat(self)
 
633
 
 
634
    def is_supported(self):
 
635
        """Format 4 is not supported.
 
636
 
 
637
        It is not supported because the model changed from 4 to 5 and the
 
638
        conversion logic is expensive - so doing it on the fly was not 
 
639
        feasible.
596
640
        """
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):
 
641
        return False
 
642
 
 
643
 
 
644
class BzrBranchFormat5(BzrBranchFormat):
608
645
    """Bzr branch format 5.
609
646
 
610
647
    This format has:
611
 
     - a revision-history file.
612
 
     - a format string
613
 
     - a lock lock file.
 
648
     - weaves for file texts and inventory
 
649
     - flat stores
 
650
     - TextStores for revisions and signatures.
614
651
    """
615
652
 
616
653
    def get_format_string(self):
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.
 
654
        """See BzrBranchFormat.get_format_string()."""
 
655
        return BZR_BRANCH_FORMAT_5
 
656
 
 
657
 
 
658
class BzrBranchFormat6(BzrBranchFormat):
 
659
    """Bzr branch format 6.
665
660
 
666
661
    This format has:
667
 
     - A location file
668
 
     - a format string
 
662
     - weaves for file texts and inventory
 
663
     - hash subdirectory based stores.
 
664
     - TextStores for revisions and signatures.
669
665
    """
670
666
 
671
667
    def get_format_string(self):
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
 
                   ]
 
668
        """See BzrBranchFormat.get_format_string()."""
 
669
        return BZR_BRANCH_FORMAT_6
 
670
 
 
671
 
 
672
BzrBranchFormat.register_format(BzrBranchFormat4())
 
673
BzrBranchFormat.register_format(BzrBranchFormat5())
 
674
BzrBranchFormat.register_format(BzrBranchFormat6())
 
675
 
 
676
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
 
677
#       make sure that ancestry.weave is deleted (it is never used, but
 
678
#       used to be created)
 
679
 
734
680
 
735
681
class BzrBranch(Branch):
736
682
    """A branch stored in the actual filesystem.
738
684
    Note that it's "local" in the context of the filesystem; it doesn't
739
685
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
740
686
    it's writable, and can be accessed via the normal filesystem API.
 
687
 
741
688
    """
742
689
    # We actually expect this class to be somewhat short-lived; part of its
743
690
    # purpose is to try to isolate what bits of the branch logic are tied to
750
697
    # This should match a prefix with a function which accepts
751
698
    REVISION_NAMESPACES = {}
752
699
 
753
 
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
 
700
    def push_stores(self, branch_to):
 
701
        """See Branch.push_stores."""
 
702
        if (not isinstance(self._branch_format, BzrBranchFormat4) or
 
703
            self._branch_format != branch_to._branch_format):
 
704
            from bzrlib.fetch import greedy_fetch
 
705
            mutter("Using fetch logic to push between %s(%s) and %s(%s)",
 
706
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
707
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
708
                         revision=self.last_revision())
 
709
            return
 
710
 
 
711
        # format 4 to format 4 logic only.
 
712
        store_pairs = ((self.text_store,      branch_to.text_store),
 
713
                       (self.inventory_store, branch_to.inventory_store),
 
714
                       (self.revision_store,  branch_to.revision_store))
 
715
        try:
 
716
            for from_store, to_store in store_pairs: 
 
717
                copy_all(from_store, to_store)
 
718
        except UnlistableStore:
 
719
            raise UnlistableBranch(from_store)
 
720
 
 
721
    def __init__(self, transport, init=DEPRECATED_PARAMETER,
754
722
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
755
 
                 _control_files=None, a_bzrdir=None):
 
723
                 _control_files=None):
756
724
        """Create new branch object at a particular location.
757
725
 
758
726
        transport -- A Transport object, defining how to access files.
765
733
            version is not applied.  This is intended only for
766
734
            upgrade/recovery type use; it's not guaranteed that
767
735
            all operations will work on old format branches.
 
736
 
 
737
        In the test suite, creation of new trees is tested using the
 
738
        `ScratchBranch` class.
768
739
        """
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('..')
 
740
        assert isinstance(transport, Transport), \
 
741
            "%r is not a Transport" % transport
 
742
        self._transport = transport
774
743
        self._base = self._transport.base
775
 
        self._format = _format
776
744
        if _control_files is None:
777
 
            raise BzrBadParameterMissing('_control_files')
 
745
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
 
746
                                           'branch-lock')
778
747
        self.control_files = _control_files
779
748
        if deprecated_passed(init):
780
749
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
789
758
        if deprecated_passed(relax_version_check):
790
759
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
791
760
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
792
 
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
 
761
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
793
762
                 "open() method.",
794
763
                 DeprecationWarning,
795
764
                 stacklevel=2)
796
765
            if (not relax_version_check
797
 
                and not self._format.is_supported()):
 
766
                and not self._branch_format.is_supported()):
798
767
                raise errors.UnsupportedFormatError(
799
768
                        'sorry, branch format %r not supported' % fmt,
800
769
                        ['use a different bzr version',
801
770
                         'or remove the .bzr directory'
802
771
                         ' and "bzr init" again'])
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()
 
772
        self.repository = Repository(transport, self._branch_format)
 
773
 
 
774
 
 
775
    @staticmethod
 
776
    def _initialize(base):
 
777
        """Create a bzr branch in the latest format."""
 
778
        return BzrBranchFormat6().initialize(base)
811
779
 
812
780
    def __str__(self):
813
781
        return '%s(%r)' % (self.__class__.__name__, self.base)
865
833
        """Identify the branch format if needed.
866
834
 
867
835
        The format is stored as a reference to the format object in
868
 
        self._format for code that needs to check it later.
 
836
        self._branch_format for code that needs to check it later.
869
837
 
870
838
        The format parameter is either None or the branch format class
871
839
        used to open this branch.
872
 
 
873
 
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
874
840
        """
875
841
        if format is None:
876
 
            format = BzrBranchFormat.find_format(self.bzrdir)
877
 
        self._format = format
878
 
        mutter("got branch format %s", self._format)
 
842
            format = BzrBranchFormat.find_format(self._transport)
 
843
        self._branch_format = format
 
844
        mutter("got branch format %s", self._branch_format)
879
845
 
880
846
    @needs_read_lock
881
847
    def get_root_id(self):
1006
972
        if (self.base.find('://') != -1 or 
1007
973
            not isinstance(self._transport, LocalTransport)):
1008
974
            raise NoWorkingTree(self.base)
1009
 
        return self.bzrdir.open_workingtree()
 
975
        return WorkingTree(self.base, branch=self)
1010
976
 
1011
977
    @needs_write_lock
1012
978
    def pull(self, source, overwrite=False):
1082
1048
        history = self._get_truncated_history(revision)
1083
1049
        if not bzrlib.osutils.lexists(to_location):
1084
1050
            os.mkdir(to_location)
1085
 
        bzrdir_to = self.bzrdir._format.initialize(to_location)
1086
 
        self.repository.clone(bzrdir_to)
1087
 
        branch_to = bzrdir_to.create_branch()
 
1051
        branch_to = BzrBranchFormat6().initialize(to_location)
1088
1052
        mutter("copy branch from %s to %s", self, branch_to)
1089
1053
 
 
1054
        self.repository.copy(branch_to.repository)
 
1055
        
 
1056
        # must be done *after* history is copied across
1090
1057
        # FIXME duplicate code with base .clone().
1091
1058
        # .. would template method be useful here?  RBC 20051207
1092
1059
        branch_to.set_parent(self.base)
1095
1062
        mutter("copied")
1096
1063
        return branch_to
1097
1064
 
 
1065
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
1066
        print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
 
1067
        if (to_branch_type is None
 
1068
            and self.repository.weave_store.listable()
 
1069
            and self.repository.revision_store.listable()):
 
1070
            return self._clone_weave(to_location, revision, basis_branch)
 
1071
        else:
 
1072
            return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
 
1073
 
 
1074
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
1075
        """Find file_id(s) which are involved in the changes between revisions.
 
1076
 
 
1077
        This determines the set of revisions which are involved, and then
 
1078
        finds all file ids affected by those revisions.
 
1079
        """
 
1080
        # TODO: jam 20060119 This code assumes that w.inclusions will
 
1081
        #       always be correct. But because of the presence of ghosts
 
1082
        #       it is possible to be wrong.
 
1083
        #       One specific example from Robert Collins:
 
1084
        #       Two branches, with revisions ABC, and AD
 
1085
        #       C is a ghost merge of D.
 
1086
        #       Inclusions doesn't recognize D as an ancestor.
 
1087
        #       If D is ever merged in the future, the weave
 
1088
        #       won't be fixed, because AD never saw revision C
 
1089
        #       to cause a conflict which would force a reweave.
 
1090
        w = self.repository.get_inventory_weave()
 
1091
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
1092
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
1093
        included = to_set.difference(from_set)
 
1094
        changed = map(w.idx_to_name, included)
 
1095
        return self._fileid_involved_by_set(changed)
 
1096
 
 
1097
    def fileid_involved(self, last_revid=None):
 
1098
        """Find all file_ids modified in the ancestry of last_revid.
 
1099
 
 
1100
        :param last_revid: If None, last_revision() will be used.
 
1101
        """
 
1102
        w = self.repository.get_inventory_weave()
 
1103
        if not last_revid:
 
1104
            changed = set(w._names)
 
1105
        else:
 
1106
            included = w.inclusions([w.lookup(last_revid)])
 
1107
            changed = map(w.idx_to_name, included)
 
1108
        return self._fileid_involved_by_set(changed)
 
1109
 
 
1110
    def fileid_involved_by_set(self, changes):
 
1111
        """Find all file_ids modified by the set of revisions passed in.
 
1112
 
 
1113
        :param changes: A set() of revision ids
 
1114
        """
 
1115
        # TODO: jam 20060119 This line does *nothing*, remove it.
 
1116
        #       or better yet, change _fileid_involved_by_set so
 
1117
        #       that it takes the inventory weave, rather than
 
1118
        #       pulling it out by itself.
 
1119
        w = self.repository.get_inventory_weave()
 
1120
        return self._fileid_involved_by_set(changes)
 
1121
 
 
1122
    def _fileid_involved_by_set(self, changes):
 
1123
        """Find the set of file-ids affected by the set of revisions.
 
1124
 
 
1125
        :param changes: A set() of revision ids.
 
1126
        :return: A set() of file ids.
 
1127
        
 
1128
        This peaks at the Weave, interpreting each line, looking to
 
1129
        see if it mentions one of the revisions. And if so, includes
 
1130
        the file id mentioned.
 
1131
        This expects both the Weave format, and the serialization
 
1132
        to have a single line per file/directory, and to have
 
1133
        fileid="" and revision="" on that line.
 
1134
        """
 
1135
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
 
1136
                isinstance(self._branch_format, BzrBranchFormat6)), \
 
1137
            "fileid_involved only supported for branches which store inventory as xml"
 
1138
 
 
1139
        w = self.repository.get_inventory_weave()
 
1140
        file_ids = set()
 
1141
        for line in w._weave:
 
1142
 
 
1143
            # it is ugly, but it is due to the weave structure
 
1144
            if not isinstance(line, basestring): continue
 
1145
 
 
1146
            start = line.find('file_id="')+9
 
1147
            if start < 9: continue
 
1148
            end = line.find('"', start)
 
1149
            assert end>= 0
 
1150
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
1151
 
 
1152
            # check if file_id is already present
 
1153
            if file_id in file_ids: continue
 
1154
 
 
1155
            start = line.find('revision="')+10
 
1156
            if start < 10: continue
 
1157
            end = line.find('"', start)
 
1158
            assert end>= 0
 
1159
            revision_id = xml.sax.saxutils.unescape(line[start:end])
 
1160
 
 
1161
            if revision_id in changes:
 
1162
                file_ids.add(file_id)
 
1163
 
 
1164
        return file_ids
 
1165
 
 
1166
 
 
1167
Branch.set_default_initializer(BzrBranch._initialize)
 
1168
 
1098
1169
 
1099
1170
class BranchTestProviderAdapter(object):
1100
1171
    """A tool to generate a suite testing multiple branch formats at once.
1112
1183
    
1113
1184
    def adapt(self, test):
1114
1185
        result = TestSuite()
1115
 
        for branch_format, bzrdir_format in self._formats:
 
1186
        for format in self._formats:
1116
1187
            new_test = deepcopy(test)
1117
1188
            new_test.transport_server = self._transport_server
1118
1189
            new_test.transport_readonly_server = self._transport_readonly_server
1119
 
            new_test.bzrdir_format = bzrdir_format
1120
 
            new_test.branch_format = branch_format
 
1190
            new_test.branch_format = format
1121
1191
            def make_new_test_id():
1122
 
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
1192
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1123
1193
                return lambda: new_id
1124
1194
            new_test.id = make_new_test_id()
1125
1195
            result.addTest(new_test)
1126
1196
        return result
1127
1197
 
1128
1198
 
 
1199
class ScratchBranch(BzrBranch):
 
1200
    """Special test class: a branch that cleans up after itself.
 
1201
 
 
1202
    >>> b = ScratchBranch()
 
1203
    >>> isdir(b.base)
 
1204
    True
 
1205
    >>> bd = b.base
 
1206
    >>> b._transport.__del__()
 
1207
    >>> isdir(bd)
 
1208
    False
 
1209
    """
 
1210
 
 
1211
    def __init__(self, files=[], dirs=[], transport=None):
 
1212
        """Make a test branch.
 
1213
 
 
1214
        This creates a temporary directory and runs init-tree in it.
 
1215
 
 
1216
        If any files are listed, they are created in the working copy.
 
1217
        """
 
1218
        if transport is None:
 
1219
            transport = bzrlib.transport.local.ScratchTransport()
 
1220
            # local import for scope restriction
 
1221
            from bzrlib.workingtree import WorkingTree
 
1222
            WorkingTree.create_standalone(transport.base)
 
1223
            super(ScratchBranch, self).__init__(transport)
 
1224
        else:
 
1225
            super(ScratchBranch, self).__init__(transport)
 
1226
 
 
1227
        # BzrBranch creates a clone to .bzr and then forgets about the
 
1228
        # original transport. A ScratchTransport() deletes itself and
 
1229
        # everything underneath it when it goes away, so we need to
 
1230
        # grab a local copy to prevent that from happening
 
1231
        self._transport = transport
 
1232
 
 
1233
        for d in dirs:
 
1234
            self._transport.mkdir(d)
 
1235
            
 
1236
        for f in files:
 
1237
            self._transport.put(f, 'content of %s' % f)
 
1238
 
 
1239
    def clone(self):
 
1240
        """
 
1241
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
1242
        >>> os.listdir(orig.base)
 
1243
        [u'.bzr', u'file1', u'file2']
 
1244
        >>> clone = orig.clone()
 
1245
        >>> if os.name != 'nt':
 
1246
        ...   os.path.samefile(orig.base, clone.base)
 
1247
        ... else:
 
1248
        ...   orig.base == clone.base
 
1249
        ...
 
1250
        False
 
1251
        >>> os.listdir(clone.base)
 
1252
        [u'.bzr', u'file1', u'file2']
 
1253
        """
 
1254
        from shutil import copytree
 
1255
        from bzrlib.osutils import mkdtemp
 
1256
        base = mkdtemp()
 
1257
        os.rmdir(base)
 
1258
        copytree(self.base, base, symlinks=True)
 
1259
        return ScratchBranch(
 
1260
            transport=bzrlib.transport.local.ScratchTransport(base))
 
1261
    
 
1262
 
1129
1263
######################################################################
1130
1264
# predicates
1131
1265
 
1132
1266
 
1133
 
@deprecated_function(zero_eight)
1134
 
def ScratchBranch(*args, **kwargs):
1135
 
    """See bzrlib.bzrdir.ScratchDir."""
1136
 
    d = ScratchDir(*args, **kwargs)
1137
 
    return d.open_branch()
1138
 
 
1139
 
 
1140
 
@deprecated_function(zero_eight)
1141
 
def is_control_file(*args, **kwargs):
1142
 
    """See bzrlib.workingtree.is_control_file."""
1143
 
    return bzrlib.workingtree.is_control_file(*args, **kwargs)
 
1267
def is_control_file(filename):
 
1268
    ## FIXME: better check
 
1269
    filename = normpath(filename)
 
1270
    while filename != '':
 
1271
        head, tail = os.path.split(filename)
 
1272
        ## mutter('check %r for control file' % ((head, tail),))
 
1273
        if tail == bzrlib.BZRDIR:
 
1274
            return True
 
1275
        if filename == head:
 
1276
            break
 
1277
        filename = head
 
1278
    return False