~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2006-03-06 11:20:10 UTC
  • mfrom: (1593 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1611.
  • Revision ID: mbp@sourcefrog.net-20060306112010-17c0170dde5d1eea
[merge] large merge to sync with bzr.dev

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
47
47
                           NoWorkingTree)
48
48
import bzrlib.inventory as inventory
49
49
from bzrlib.inventory import Inventory
50
 
from bzrlib.lockable_files import LockableFiles
 
50
from bzrlib.lockable_files import LockableFiles, TransportLock
51
51
from bzrlib.osutils import (isdir, quotefn,
52
52
                            rename, splitpath, sha_file,
53
53
                            file_kind, abspath, normpath, pathjoin,
57
57
from bzrlib.trace import mutter, note
58
58
from bzrlib.tree import EmptyTree, RevisionTree
59
59
from bzrlib.repository import Repository
60
 
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
 
60
from bzrlib.revision import (
 
61
                             get_intervening_revisions,
 
62
                             is_ancestor,
 
63
                             NULL_REVISION,
 
64
                             Revision,
 
65
                             )
61
66
from bzrlib.store import copy_all
62
67
from bzrlib.symbol_versioning import *
63
68
import bzrlib.transactions as transactions
94
99
    # - RBC 20060112
95
100
    base = None
96
101
 
97
 
    _default_initializer = None
98
 
    """The default initializer for making new branches."""
99
 
 
100
102
    def __init__(self, *ignored, **ignored_too):
101
103
        raise NotImplementedError('The Branch class is abstract')
102
104
 
103
105
    @staticmethod
 
106
    @deprecated_method(zero_eight)
104
107
    def open_downlevel(base):
105
108
        """Open a branch which may be of an old format."""
106
109
        return Branch.open(base, _unsupported=True)
107
110
        
108
111
    @staticmethod
109
112
    def open(base, _unsupported=False):
110
 
        """Open an existing branch, rooted at 'base' (url)
111
 
        
112
 
        _unsupported is a private parameter to the Branch class.
 
113
        """Open the repository rooted at base.
 
114
 
 
115
        For instance, if the repository is at URL/.bzr/repository,
 
116
        Repository.open(URL) -> a Repository instance.
113
117
        """
114
 
        t = get_transport(base)
115
 
        mutter("trying to open %r with transport %r", base, t)
116
 
        format = BzrBranchFormat.find_format(t)
117
 
        if not _unsupported and not format.is_supported():
118
 
            # see open_downlevel to open legacy branches.
119
 
            raise errors.UnsupportedFormatError(
120
 
                    'sorry, branch format %s not supported' % format,
121
 
                    ['use a different bzr version',
122
 
                     'or remove the .bzr directory'
123
 
                     ' and "bzr init" again'])
124
 
        return format.open(t)
 
118
        control = bzrdir.BzrDir.open(base, _unsupported)
 
119
        return control.open_branch(_unsupported)
125
120
 
126
121
    @staticmethod
127
122
    def open_containing(url):
135
130
        format, UnknownFormatError or UnsupportedFormatError are raised.
136
131
        If there is one, it is returned, along with the unused portion of url.
137
132
        """
138
 
        t = get_transport(url)
139
 
        # this gets the normalised url back. I.e. '.' -> the full path.
140
 
        url = t.base
141
 
        while True:
142
 
            try:
143
 
                format = BzrBranchFormat.find_format(t)
144
 
                return format.open(t), t.relpath(url)
145
 
            except NotBranchError, e:
146
 
                mutter('not a branch in: %r %s', t.base, e)
147
 
            new_t = t.clone('..')
148
 
            if new_t.base == t.base:
149
 
                # reached the root, whatever that may be
150
 
                raise NotBranchError(path=url)
151
 
            t = new_t
152
 
 
153
 
    @staticmethod
154
 
    def create(base):
155
 
        """Create a new Branch at the url 'bzr'.
156
 
        
157
 
        This will call the current default initializer with base
158
 
        as the only parameter.
159
 
        """
160
 
        return Branch._default_initializer(safe_unicode(base))
 
133
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
134
        return control.open_branch(), relpath
161
135
 
162
136
    @staticmethod
163
137
    @deprecated_function(zero_eight)
167
141
        NOTE: This will soon be deprecated in favour of creation
168
142
        through a BzrDir.
169
143
        """
170
 
        # imported here to prevent scope creep as this is going.
171
 
        from bzrlib.workingtree import WorkingTree
172
 
        return WorkingTree.create_standalone(safe_unicode(base)).branch
173
 
 
174
 
    @staticmethod
175
 
    def get_default_initializer():
176
 
        """Return the initializer being used for new branches."""
177
 
        return Branch._default_initializer
178
 
 
179
 
    @staticmethod
180
 
    def set_default_initializer(initializer):
181
 
        """Set the initializer to be used for new branches."""
182
 
        Branch._default_initializer = staticmethod(initializer)
 
144
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
183
145
 
184
146
    def setup_caching(self, cache_root):
185
147
        """Subclasses that care about caching should override this, and set
200
162
 
201
163
    nick = property(_get_nick, _set_nick)
202
164
        
203
 
    def push_stores(self, branch_to):
204
 
        """Copy the content of this branches store to branch_to."""
205
 
        raise NotImplementedError('push_stores is abstract')
206
 
 
207
165
    def lock_write(self):
208
166
        raise NotImplementedError('lock_write is abstract')
209
167
        
225
183
        """
226
184
        raise NotImplementedError('abspath is abstract')
227
185
 
 
186
    def bind(self, other):
 
187
        """Bind the local branch the other branch.
 
188
 
 
189
        :param other: The branch to bind to
 
190
        :type other: Branch
 
191
        """
 
192
        raise errors.UpgradeRequired(self.base)
 
193
 
 
194
    @needs_write_lock
 
195
    def fetch(self, from_branch, last_revision=None, pb=None):
 
196
        """Copy revisions from from_branch into this branch.
 
197
 
 
198
        :param from_branch: Where to copy from.
 
199
        :param last_revision: What revision to stop at (None for at the end
 
200
                              of the branch.
 
201
        :param pb: An optional progress bar to use.
 
202
 
 
203
        Returns the copied revision count and the failed revisions in a tuple:
 
204
        (copied, failures).
 
205
        """
 
206
        if self.base == from_branch.base:
 
207
            raise Exception("can't fetch from a branch to itself %s, %s" % 
 
208
                            (self.base, to_branch.base))
 
209
        if pb is None:
 
210
            pb = bzrlib.ui.ui_factory.progress_bar()
 
211
 
 
212
        from_branch.lock_read()
 
213
        try:
 
214
            if last_revision is None:
 
215
                pb.update('get source history')
 
216
                from_history = from_branch.revision_history()
 
217
                if from_history:
 
218
                    last_revision = from_history[-1]
 
219
                else:
 
220
                    # no history in the source branch
 
221
                    last_revision = NULL_REVISION
 
222
            return self.repository.fetch(from_branch.repository,
 
223
                                         revision_id=last_revision,
 
224
                                         pb=pb)
 
225
        finally:
 
226
            from_branch.unlock()
 
227
 
 
228
    def get_bound_location(self):
 
229
        """Return the URL of the rbanch we are bound to.
 
230
 
 
231
        Older format branches cannot bind, please be sure to use a metadir
 
232
        branch.
 
233
        """
 
234
        return None
 
235
 
 
236
    def get_master_branch(self):
 
237
        """Return the branch we are bound to.
 
238
        
 
239
        :return: Either a Branch, or None
 
240
        """
 
241
        return None
 
242
 
228
243
    def get_root_id(self):
229
244
        """Return the id of this branches root"""
230
245
        raise NotImplementedError('get_root_id is abstract')
251
266
        """
252
267
        return len(self.revision_history())
253
268
 
 
269
    def unbind(self):
 
270
        """Older format branches cannot bind or unbind."""
 
271
        raise errors.UpgradeRequired(self.base)
 
272
 
254
273
    def last_revision(self):
255
274
        """Return last patch hash, or None if no history."""
256
275
        ph = self.revision_history()
259
278
        else:
260
279
            return None
261
280
 
262
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
281
    def missing_revisions(self, other, stop_revision=None):
263
282
        """Return a list of new revisions that would perfectly fit.
264
283
        
265
284
        If self and other have not diverged, return a list of the revisions
266
285
        present in other, but missing from self.
267
286
 
 
287
        >>> from bzrlib.workingtree import WorkingTree
268
288
        >>> bzrlib.trace.silent = True
269
 
        >>> br1 = ScratchBranch()
270
 
        >>> br2 = ScratchBranch()
 
289
        >>> d1 = bzrdir.ScratchDir()
 
290
        >>> br1 = d1.open_branch()
 
291
        >>> wt1 = d1.open_workingtree()
 
292
        >>> d2 = bzrdir.ScratchDir()
 
293
        >>> br2 = d2.open_branch()
 
294
        >>> wt2 = d2.open_workingtree()
271
295
        >>> br1.missing_revisions(br2)
272
296
        []
273
 
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
 
297
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
274
298
        >>> br1.missing_revisions(br2)
275
299
        [u'REVISION-ID-1']
276
300
        >>> br2.missing_revisions(br1)
277
301
        []
278
 
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
 
302
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
279
303
        >>> br1.missing_revisions(br2)
280
304
        []
281
 
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
 
305
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
282
306
        >>> br1.missing_revisions(br2)
283
307
        [u'REVISION-ID-2A']
284
 
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
 
308
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
285
309
        >>> br1.missing_revisions(br2)
286
310
        Traceback (most recent call last):
287
311
        DivergedBranches: These branches have diverged.  Try merge.
304
328
        return other_history[self_len:stop_revision]
305
329
 
306
330
    def update_revisions(self, other, stop_revision=None):
307
 
        """Pull in new perfect-fit revisions."""
 
331
        """Pull in new perfect-fit revisions.
 
332
 
 
333
        :param other: Another Branch to pull from
 
334
        :param stop_revision: Updated until the given revision
 
335
        :return: None
 
336
        """
308
337
        raise NotImplementedError('update_revisions is abstract')
309
338
 
310
339
    def pullable_revisions(self, other, stop_revision):
330
359
            raise bzrlib.errors.NoSuchRevision(self, revno)
331
360
        return history[revno - 1]
332
361
 
333
 
    def working_tree(self):
334
 
        """Return a `Tree` for the working copy if this is a local branch."""
335
 
        raise NotImplementedError('working_tree is abstract')
336
 
 
337
 
    def pull(self, source, overwrite=False):
 
362
    def pull(self, source, overwrite=False, stop_revision=None):
338
363
        raise NotImplementedError('pull is abstract')
339
364
 
340
365
    def basis_tree(self):
387
412
    def set_parent(self, url):
388
413
        raise NotImplementedError('set_parent is abstract')
389
414
 
 
415
    @needs_write_lock
 
416
    def update(self):
 
417
        """Synchronise this branch with the master branch if any. 
 
418
 
 
419
        :return: None or the last_revision pivoted out during the update.
 
420
        """
 
421
        return None
 
422
 
390
423
    def check_revno(self, revno):
391
424
        """\
392
425
        Check whether a revno corresponds to any revision.
402
435
        """
403
436
        if revno < 1 or revno > self.revno():
404
437
            raise InvalidRevisionNumber(revno)
405
 
        
406
 
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
407
 
        """Copy this branch into the existing directory to_location.
408
 
 
409
 
        Returns the newly created branch object.
410
 
 
411
 
        revision
412
 
            If not None, only revisions up to this point will be copied.
413
 
            The head of the new branch will be that revision.  Must be a
414
 
            revid or None.
415
 
    
416
 
        to_location -- The destination directory; must either exist and be 
417
 
            empty, or not exist, in which case it is created.
418
 
    
419
 
        basis_branch
420
 
            A local branch to copy revisions from, related to this branch. 
421
 
            This is used when branching from a remote (slow) branch, and we have
422
 
            a local branch that might contain some relevant revisions.
423
 
    
424
 
        to_branch_type
425
 
            Branch type of destination branch
426
 
        """
427
 
        from bzrlib.workingtree import WorkingTree
428
 
        assert isinstance(to_location, basestring)
429
 
        if not bzrlib.osutils.lexists(to_location):
430
 
            os.mkdir(to_location)
431
 
        if to_branch_type is None:
432
 
            to_branch_type = BzrBranch
433
 
        print "FIXME use a branch format here"
434
 
        br_to = to_branch_type.initialize(to_location)
435
 
        mutter("copy branch from %s to %s", self, br_to)
436
 
        if basis_branch is not None:
437
 
            basis_branch.push_stores(br_to)
438
 
        if revision is None:
439
 
            revision = self.last_revision()
440
 
        br_to.update_revisions(self, stop_revision=revision)
441
 
        br_to.set_parent(self.base)
442
 
        WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
443
 
        mutter("copied")
444
 
        return br_to
445
 
 
446
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
447
 
        """ This function returns the file_id(s) involved in the
448
 
            changes between the from_revid revision and the to_revid
449
 
            revision
450
 
        """
451
 
        raise NotImplementedError('fileid_involved_between_revs is abstract')
452
 
 
453
 
    def fileid_involved(self, last_revid=None):
454
 
        """ This function returns the file_id(s) involved in the
455
 
            changes up to the revision last_revid
456
 
            If no parametr is passed, then all file_id[s] present in the
457
 
            repository are returned
458
 
        """
459
 
        raise NotImplementedError('fileid_involved is abstract')
460
 
 
461
 
    def fileid_involved_by_set(self, changes):
462
 
        """ This function returns the file_id(s) involved in the
463
 
            changes present in the set 'changes'
464
 
        """
465
 
        raise NotImplementedError('fileid_involved_by_set is abstract')
466
 
 
467
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
468
 
        """ This function returns the file_id(s) involved in the
469
 
            changes between the from_revid revision and the to_revid
470
 
            revision
471
 
        """
472
 
        raise NotImplementedError('fileid_involved_between_revs is abstract')
473
 
 
474
 
    def fileid_involved(self, last_revid=None):
475
 
        """ This function returns the file_id(s) involved in the
476
 
            changes up to the revision last_revid
477
 
            If no parametr is passed, then all file_id[s] present in the
478
 
            repository are returned
479
 
        """
480
 
        raise NotImplementedError('fileid_involved is abstract')
481
 
 
482
 
    def fileid_involved_by_set(self, changes):
483
 
        """ This function returns the file_id(s) involved in the
484
 
            changes present in the set 'changes'
485
 
        """
486
 
        raise NotImplementedError('fileid_involved_by_set is abstract')
487
 
 
488
 
class BzrBranchFormat(object):
 
438
 
 
439
    @needs_read_lock
 
440
    def clone(self, *args, **kwargs):
 
441
        """Clone this branch into to_bzrdir preserving all semantic values.
 
442
        
 
443
        revision_id: if not None, the revision history in the new branch will
 
444
                     be truncated to end with revision_id.
 
445
        """
 
446
        # for API compatability, until 0.8 releases we provide the old api:
 
447
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
448
        # after 0.8 releases, the *args and **kwargs should be changed:
 
449
        # def clone(self, to_bzrdir, revision_id=None):
 
450
        if (kwargs.get('to_location', None) or
 
451
            kwargs.get('revision', None) or
 
452
            kwargs.get('basis_branch', None) or
 
453
            (len(args) and isinstance(args[0], basestring))):
 
454
            # backwards compatability api:
 
455
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
456
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
457
            # get basis_branch
 
458
            if len(args) > 2:
 
459
                basis_branch = args[2]
 
460
            else:
 
461
                basis_branch = kwargs.get('basis_branch', None)
 
462
            if basis_branch:
 
463
                basis = basis_branch.bzrdir
 
464
            else:
 
465
                basis = None
 
466
            # get revision
 
467
            if len(args) > 1:
 
468
                revision_id = args[1]
 
469
            else:
 
470
                revision_id = kwargs.get('revision', None)
 
471
            # get location
 
472
            if len(args):
 
473
                url = args[0]
 
474
            else:
 
475
                # no default to raise if not provided.
 
476
                url = kwargs.get('to_location')
 
477
            return self.bzrdir.clone(url,
 
478
                                     revision_id=revision_id,
 
479
                                     basis=basis).open_branch()
 
480
        # new cleaner api.
 
481
        # generate args by hand 
 
482
        if len(args) > 1:
 
483
            revision_id = args[1]
 
484
        else:
 
485
            revision_id = kwargs.get('revision_id', None)
 
486
        if len(args):
 
487
            to_bzrdir = args[0]
 
488
        else:
 
489
            # no default to raise if not provided.
 
490
            to_bzrdir = kwargs.get('to_bzrdir')
 
491
        result = self._format.initialize(to_bzrdir)
 
492
        self.copy_content_into(result, revision_id=revision_id)
 
493
        return  result
 
494
 
 
495
    @needs_read_lock
 
496
    def sprout(self, to_bzrdir, revision_id=None):
 
497
        """Create a new line of development from the branch, into to_bzrdir.
 
498
        
 
499
        revision_id: if not None, the revision history in the new branch will
 
500
                     be truncated to end with revision_id.
 
501
        """
 
502
        result = self._format.initialize(to_bzrdir)
 
503
        self.copy_content_into(result, revision_id=revision_id)
 
504
        result.set_parent(self.bzrdir.root_transport.base)
 
505
        return result
 
506
 
 
507
    @needs_read_lock
 
508
    def copy_content_into(self, destination, revision_id=None):
 
509
        """Copy the content of self into destination.
 
510
 
 
511
        revision_id: if not None, the revision history in the new branch will
 
512
                     be truncated to end with revision_id.
 
513
        """
 
514
        new_history = self.revision_history()
 
515
        if revision_id is not None:
 
516
            try:
 
517
                new_history = new_history[:new_history.index(revision_id) + 1]
 
518
            except ValueError:
 
519
                rev = self.repository.get_revision(revision_id)
 
520
                new_history = rev.get_history(self.repository)[1:]
 
521
        destination.set_revision_history(new_history)
 
522
        parent = self.get_parent()
 
523
        if parent:
 
524
            destination.set_parent(parent)
 
525
 
 
526
 
 
527
class BranchFormat(object):
489
528
    """An encapsulation of the initialization and open routines for a format.
490
529
 
491
530
    Formats provide three things:
503
542
    object will be created every time regardless.
504
543
    """
505
544
 
 
545
    _default_format = None
 
546
    """The default format used for new branches."""
 
547
 
506
548
    _formats = {}
507
549
    """The known formats."""
508
550
 
509
551
    @classmethod
510
 
    def find_format(klass, transport):
511
 
        """Return the format registered for URL."""
 
552
    def find_format(klass, a_bzrdir):
 
553
        """Return the format for the branch object in a_bzrdir."""
512
554
        try:
513
 
            format_string = transport.get(".bzr/branch-format").read()
 
555
            transport = a_bzrdir.get_branch_transport(None)
 
556
            format_string = transport.get("format").read()
514
557
            return klass._formats[format_string]
515
558
        except NoSuchFile:
516
559
            raise NotBranchError(path=transport.base)
517
560
        except KeyError:
518
561
            raise errors.UnknownFormatError(format_string)
519
562
 
 
563
    @classmethod
 
564
    def get_default_format(klass):
 
565
        """Return the current default format."""
 
566
        return klass._default_format
 
567
 
520
568
    def get_format_string(self):
521
569
        """Return the ASCII format string that identifies this format."""
522
570
        raise NotImplementedError(self.get_format_string)
523
571
 
524
 
    def _find_modes(self, t):
525
 
        """Determine the appropriate modes for files and directories.
526
 
        
527
 
        FIXME: When this merges into, or from storage,
528
 
        this code becomes delgatable to a LockableFiles instance.
529
 
 
530
 
        For now its cribbed and returns (dir_mode, file_mode)
531
 
        """
532
 
        try:
533
 
            st = t.stat('.')
534
 
        except errors.TransportNotPossible:
535
 
            dir_mode = 0755
536
 
            file_mode = 0644
537
 
        else:
538
 
            dir_mode = st.st_mode & 07777
539
 
            # Remove the sticky and execute bits for files
540
 
            file_mode = dir_mode & ~07111
541
 
        if not BzrBranch._set_dir_mode:
542
 
            dir_mode = None
543
 
        if not BzrBranch._set_file_mode:
544
 
            file_mode = None
545
 
        return dir_mode, file_mode
546
 
 
547
 
    def initialize(self, url):
548
 
        """Create a branch of this format at url and return an open branch."""
549
 
        t = get_transport(url)
550
 
        from bzrlib.weavefile import write_weave_v5
551
 
        from bzrlib.weave import Weave
552
 
        
553
 
        # Create an empty weave
554
 
        sio = StringIO()
555
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
556
 
        empty_weave = sio.getvalue()
557
 
 
558
 
        # Since we don't have a .bzr directory, inherit the
559
 
        # mode from the root directory
560
 
        temp_control = LockableFiles(t, '')
561
 
        temp_control._transport.mkdir('.bzr',
562
 
                                      mode=temp_control._dir_mode)
563
 
        file_mode = temp_control._file_mode
564
 
        del temp_control
565
 
        mutter('created control directory in ' + t.base)
566
 
        control = t.clone('.bzr')
567
 
        dirs = ['revision-store', 'weaves']
568
 
        lock_file = 'branch-lock'
569
 
        utf8_files = [('README', 
570
 
                       "This is a Bazaar-NG control directory.\n"
571
 
                       "Do not change any files in this directory.\n"),
572
 
                      ('branch-format', self.get_format_string()),
573
 
                      ('revision-history', ''),
574
 
                      ('branch-name', ''),
575
 
                      ]
576
 
        files = [('inventory.weave', StringIO(empty_weave)), 
577
 
                 ]
578
 
        
579
 
        # FIXME: RBC 20060125 dont peek under the covers
580
 
        # NB: no need to escape relative paths that are url safe.
581
 
        control.put(lock_file, StringIO(), mode=file_mode)
582
 
        control_files = LockableFiles(control, lock_file)
583
 
        control_files.lock_write()
584
 
        control_files._transport.mkdir_multi(dirs,
585
 
                mode=control_files._dir_mode)
586
 
        try:
587
 
            for file, content in utf8_files:
588
 
                control_files.put_utf8(file, content)
589
 
            for file, content in files:
590
 
                control_files.put(file, content)
591
 
        finally:
592
 
            control_files.unlock()
593
 
        return BzrBranch(t, _format=self, _control_files=control_files)
 
572
    def initialize(self, a_bzrdir):
 
573
        """Create a branch of this format in a_bzrdir."""
 
574
        raise NotImplementedError(self.initialized)
594
575
 
595
576
    def is_supported(self):
596
577
        """Is this format supported?
601
582
        """
602
583
        return True
603
584
 
604
 
    def open(self, transport):
605
 
        """Fill out the data in branch for the branch at url."""
606
 
        return BzrBranch(transport, _format=self)
 
585
    def open(self, a_bzrdir, _found=False):
 
586
        """Return the branch object for a_bzrdir
 
587
 
 
588
        _found is a private parameter, do not use it. It is used to indicate
 
589
               if format probing has already be done.
 
590
        """
 
591
        raise NotImplementedError(self.open)
607
592
 
608
593
    @classmethod
609
594
    def register_format(klass, format):
610
595
        klass._formats[format.get_format_string()] = format
611
596
 
612
597
    @classmethod
 
598
    def set_default_format(klass, format):
 
599
        klass._default_format = format
 
600
 
 
601
    @classmethod
613
602
    def unregister_format(klass, format):
614
603
        assert klass._formats[format.get_format_string()] is format
615
604
        del klass._formats[format.get_format_string()]
616
605
 
617
606
 
618
 
class BzrBranchFormat4(BzrBranchFormat):
 
607
class BzrBranchFormat4(BranchFormat):
619
608
    """Bzr branch format 4.
620
609
 
621
610
    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.
 
611
     - a revision-history file.
 
612
     - a branch-lock lock file [ to be shared with the bzrdir ]
627
613
    """
628
614
 
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.
 
615
    def initialize(self, a_bzrdir):
 
616
        """Create a branch of this format in a_bzrdir."""
 
617
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
618
        branch_transport = a_bzrdir.get_branch_transport(self)
 
619
        utf8_files = [('revision-history', ''),
 
620
                      ('branch-name', ''),
 
621
                      ]
 
622
        control_files = LockableFiles(branch_transport, 'branch-lock',
 
623
                                      TransportLock)
 
624
        control_files.create_lock()
 
625
        control_files.lock_write()
 
626
        try:
 
627
            for file, content in utf8_files:
 
628
                control_files.put_utf8(file, content)
 
629
        finally:
 
630
            control_files.unlock()
 
631
        return self.open(a_bzrdir, _found=True)
 
632
 
 
633
    def __init__(self):
 
634
        super(BzrBranchFormat4, self).__init__()
 
635
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
636
 
 
637
    def open(self, a_bzrdir, _found=False):
 
638
        """Return the branch object for a_bzrdir
 
639
 
 
640
        _found is a private parameter, do not use it. It is used to indicate
 
641
               if format probing has already be done.
643
642
        """
644
 
        return False
645
 
 
646
 
 
647
 
class BzrBranchFormat5(BzrBranchFormat):
 
643
        if not _found:
 
644
            # we are being called directly and must probe.
 
645
            raise NotImplementedError
 
646
        return BzrBranch(_format=self,
 
647
                         _control_files=a_bzrdir._control_files,
 
648
                         a_bzrdir=a_bzrdir,
 
649
                         _repository=a_bzrdir.open_repository())
 
650
 
 
651
 
 
652
class BzrBranchFormat5(BranchFormat):
648
653
    """Bzr branch format 5.
649
654
 
650
655
    This format has:
651
 
     - weaves for file texts and inventory
652
 
     - flat stores
653
 
     - TextStores for revisions and signatures.
 
656
     - a revision-history file.
 
657
     - a format string
 
658
     - a lock file.
 
659
     - works with shared repositories.
654
660
    """
655
661
 
656
662
    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.
 
663
        """See BranchFormat.get_format_string()."""
 
664
        return "Bazaar-NG branch format 5\n"
 
665
        
 
666
    def initialize(self, a_bzrdir):
 
667
        """Create a branch of this format in a_bzrdir."""
 
668
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
669
        branch_transport = a_bzrdir.get_branch_transport(self)
 
670
 
 
671
        utf8_files = [('revision-history', ''),
 
672
                      ('branch-name', ''),
 
673
                      ]
 
674
        control_files = LockableFiles(branch_transport, 'lock', TransportLock)
 
675
        control_files.create_lock()
 
676
        control_files.lock_write()
 
677
        control_files.put_utf8('format', self.get_format_string())
 
678
        try:
 
679
            for file, content in utf8_files:
 
680
                control_files.put_utf8(file, content)
 
681
        finally:
 
682
            control_files.unlock()
 
683
        return self.open(a_bzrdir, _found=True, )
 
684
 
 
685
    def __init__(self):
 
686
        super(BzrBranchFormat5, self).__init__()
 
687
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
688
 
 
689
    def open(self, a_bzrdir, _found=False):
 
690
        """Return the branch object for a_bzrdir
 
691
 
 
692
        _found is a private parameter, do not use it. It is used to indicate
 
693
               if format probing has already be done.
 
694
        """
 
695
        if not _found:
 
696
            format = BranchFormat.find_format(a_bzrdir)
 
697
            assert format.__class__ == self.__class__
 
698
        transport = a_bzrdir.get_branch_transport(None)
 
699
        control_files = LockableFiles(transport, 'lock', TransportLock)
 
700
        return BzrBranch5(_format=self,
 
701
                          _control_files=control_files,
 
702
                          a_bzrdir=a_bzrdir,
 
703
                          _repository=a_bzrdir.find_repository())
 
704
 
 
705
    def __str__(self):
 
706
        return "Bazaar-NG Metadir branch format 5"
 
707
 
 
708
 
 
709
class BranchReferenceFormat(BranchFormat):
 
710
    """Bzr branch reference format.
 
711
 
 
712
    Branch references are used in implementing checkouts, they
 
713
    act as an alias to the real branch which is at some other url.
663
714
 
664
715
    This format has:
665
 
     - weaves for file texts and inventory
666
 
     - hash subdirectory based stores.
667
 
     - TextStores for revisions and signatures.
 
716
     - A location file
 
717
     - a format string
668
718
    """
669
719
 
670
720
    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
 
 
 
721
        """See BranchFormat.get_format_string()."""
 
722
        return "Bazaar-NG Branch Reference Format 1\n"
 
723
        
 
724
    def initialize(self, a_bzrdir, target_branch=None):
 
725
        """Create a branch of this format in a_bzrdir."""
 
726
        if target_branch is None:
 
727
            # this format does not implement branch itself, thus the implicit
 
728
            # creation contract must see it as uninitializable
 
729
            raise errors.UninitializableFormat(self)
 
730
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
731
        branch_transport = a_bzrdir.get_branch_transport(self)
 
732
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
733
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
734
        branch_transport.put('format', StringIO(self.get_format_string()))
 
735
        return self.open(a_bzrdir, _found=True)
 
736
 
 
737
    def __init__(self):
 
738
        super(BranchReferenceFormat, self).__init__()
 
739
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
740
 
 
741
    def _make_reference_clone_function(format, a_branch):
 
742
        """Create a clone() routine for a branch dynamically."""
 
743
        def clone(to_bzrdir, revision_id=None):
 
744
            """See Branch.clone()."""
 
745
            return format.initialize(to_bzrdir, a_branch)
 
746
            # cannot obey revision_id limits when cloning a reference ...
 
747
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
748
            # emit some sort of warning/error to the caller ?!
 
749
        return clone
 
750
 
 
751
    def open(self, a_bzrdir, _found=False):
 
752
        """Return the branch that the branch reference in a_bzrdir points at.
 
753
 
 
754
        _found is a private parameter, do not use it. It is used to indicate
 
755
               if format probing has already be done.
 
756
        """
 
757
        if not _found:
 
758
            format = BranchFormat.find_format(a_bzrdir)
 
759
            assert format.__class__ == self.__class__
 
760
        transport = a_bzrdir.get_branch_transport(None)
 
761
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
762
        result = real_bzrdir.open_branch()
 
763
        # this changes the behaviour of result.clone to create a new reference
 
764
        # rather than a copy of the content of the branch.
 
765
        # I did not use a proxy object because that needs much more extensive
 
766
        # testing, and we are only changing one behaviour at the moment.
 
767
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
768
        # then this should be refactored to introduce a tested proxy branch
 
769
        # and a subclass of that for use in overriding clone() and ....
 
770
        # - RBC 20060210
 
771
        result.clone = self._make_reference_clone_function(result)
 
772
        return result
 
773
 
 
774
 
 
775
# formats which have no format string are not discoverable
 
776
# and not independently creatable, so are not registered.
 
777
__default_format = BzrBranchFormat5()
 
778
BranchFormat.register_format(__default_format)
 
779
BranchFormat.register_format(BranchReferenceFormat())
 
780
BranchFormat.set_default_format(__default_format)
 
781
_legacy_formats = [BzrBranchFormat4(),
 
782
                   ]
683
783
 
684
784
class BzrBranch(Branch):
685
785
    """A branch stored in the actual filesystem.
687
787
    Note that it's "local" in the context of the filesystem; it doesn't
688
788
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
689
789
    it's writable, and can be accessed via the normal filesystem API.
690
 
 
691
790
    """
692
 
    # We actually expect this class to be somewhat short-lived; part of its
693
 
    # purpose is to try to isolate what bits of the branch logic are tied to
694
 
    # filesystem access, so that in a later step, we can extricate them to
695
 
    # a separarte ("storage") class.
696
 
    _inventory_weave = None
697
791
    
698
 
    # Map some sort of prefix into a namespace
699
 
    # stuff like "revno:10", "revid:", etc.
700
 
    # This should match a prefix with a function which accepts
701
 
    REVISION_NAMESPACES = {}
702
 
 
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,
 
792
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
725
793
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
726
 
                 _control_files=None):
 
794
                 _control_files=None, a_bzrdir=None, _repository=None):
727
795
        """Create new branch object at a particular location.
728
796
 
729
797
        transport -- A Transport object, defining how to access files.
736
804
            version is not applied.  This is intended only for
737
805
            upgrade/recovery type use; it's not guaranteed that
738
806
            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
807
        """
743
 
        assert isinstance(transport, Transport), \
744
 
            "%r is not a Transport" % transport
745
 
        self._transport = transport
 
808
        if a_bzrdir is None:
 
809
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
810
        else:
 
811
            self.bzrdir = a_bzrdir
 
812
        self._transport = self.bzrdir.transport.clone('..')
746
813
        self._base = self._transport.base
 
814
        self._format = _format
747
815
        if _control_files is None:
748
 
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
749
 
                                           'branch-lock')
 
816
            raise BzrBadParameterMissing('_control_files')
750
817
        self.control_files = _control_files
751
818
        if deprecated_passed(init):
752
819
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
761
828
        if deprecated_passed(relax_version_check):
762
829
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
763
830
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
764
 
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
831
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
765
832
                 "open() method.",
766
833
                 DeprecationWarning,
767
834
                 stacklevel=2)
768
835
            if (not relax_version_check
769
 
                and not self._branch_format.is_supported()):
 
836
                and not self._format.is_supported()):
770
837
                raise errors.UnsupportedFormatError(
771
838
                        'sorry, branch format %r not supported' % fmt,
772
839
                        ['use a different bzr version',
773
840
                         'or remove the .bzr directory'
774
841
                         ' 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)
 
842
        if deprecated_passed(transport):
 
843
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
844
                 "parameter is deprecated as of bzr 0.8. "
 
845
                 "Please use Branch.open, or bzrdir.open_branch().",
 
846
                 DeprecationWarning,
 
847
                 stacklevel=2)
 
848
        self.repository = _repository
782
849
 
783
850
    def __str__(self):
784
851
        return '%s(%r)' % (self.__class__.__name__, self.base)
836
903
        """Identify the branch format if needed.
837
904
 
838
905
        The format is stored as a reference to the format object in
839
 
        self._branch_format for code that needs to check it later.
 
906
        self._format for code that needs to check it later.
840
907
 
841
908
        The format parameter is either None or the branch format class
842
909
        used to open this branch.
 
910
 
 
911
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
843
912
        """
844
913
        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)
 
914
            format = BzrBranchFormat.find_format(self.bzrdir)
 
915
        self._format = format
 
916
        mutter("got branch format %s", self._format)
848
917
 
849
918
    @needs_read_lock
850
919
    def get_root_id(self):
866
935
        # TODO: test for failed two phase locks. This is known broken.
867
936
        self.repository.unlock()
868
937
        self.control_files.unlock()
869
 
 
 
938
        
870
939
    def peek_lock_mode(self):
871
940
        if self.control_files._lock_count == 0:
872
941
            return None
932
1001
 
933
1002
    def update_revisions(self, other, stop_revision=None):
934
1003
        """See Branch.update_revisions."""
935
 
        from bzrlib.fetch import greedy_fetch
936
1004
        if stop_revision is None:
937
1005
            stop_revision = other.last_revision()
938
1006
        ### Should this be checking is_ancestor instead of revision_history?
939
1007
        if (stop_revision is not None and 
940
1008
            stop_revision in self.revision_history()):
941
1009
            return
942
 
        greedy_fetch(to_branch=self, from_branch=other,
943
 
                     revision=stop_revision)
 
1010
        self.fetch(other, stop_revision)
944
1011
        pullable_revs = self.pullable_revisions(other, stop_revision)
945
1012
        if len(pullable_revs) > 0:
946
1013
            self.append_revision(*pullable_revs)
947
1014
 
948
1015
    def pullable_revisions(self, other, stop_revision):
949
 
        """See Branch.pullable_revisions."""
950
1016
        other_revno = other.revision_id_to_revno(stop_revision)
951
1017
        try:
952
1018
            return self.missing_revisions(other, other_revno)
965
1031
        
966
1032
    def basis_tree(self):
967
1033
        """See Branch.basis_tree."""
968
 
        try:
969
 
            revision_id = self.revision_history()[-1]
970
 
            # FIXME: This is an abstraction violation, the basis tree 
971
 
            # here as defined is on the working tree, the method should
972
 
            # be too. The basis tree for a branch can be different than
973
 
            # that for a working tree. RBC 20051207
974
 
            xml = self.working_tree().read_basis_inventory(revision_id)
975
 
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
976
 
            return RevisionTree(self.repository, inv, revision_id)
977
 
        except (IndexError, NoSuchFile, NoWorkingTree), e:
978
 
            return self.repository.revision_tree(self.last_revision())
 
1034
        return self.repository.revision_tree(self.last_revision())
979
1035
 
 
1036
    @deprecated_method(zero_eight)
980
1037
    def working_tree(self):
981
 
        """See Branch.working_tree."""
 
1038
        """Create a Working tree object for this branch."""
982
1039
        from bzrlib.workingtree import WorkingTree
983
1040
        from bzrlib.transport.local import LocalTransport
984
1041
        if (self.base.find('://') != -1 or 
985
1042
            not isinstance(self._transport, LocalTransport)):
986
1043
            raise NoWorkingTree(self.base)
987
 
        return WorkingTree(self.base, branch=self)
 
1044
        return self.bzrdir.open_workingtree()
988
1045
 
989
1046
    @needs_write_lock
990
 
    def pull(self, source, overwrite=False):
 
1047
    def pull(self, source, overwrite=False, stop_revision=None):
991
1048
        """See Branch.pull."""
992
1049
        source.lock_read()
993
1050
        try:
994
1051
            old_count = len(self.revision_history())
995
1052
            try:
996
 
                self.update_revisions(source)
 
1053
                self.update_revisions(source,stop_revision)
997
1054
            except DivergedBranches:
998
1055
                if not overwrite:
999
1056
                    raise
1039
1096
    def tree_config(self):
1040
1097
        return TreeConfig(self)
1041
1098
 
1042
 
    def _get_truncated_history(self, revision_id):
1043
 
        history = self.revision_history()
1044
 
        if revision_id is None:
1045
 
            return history
 
1099
 
 
1100
class BzrBranch5(BzrBranch):
 
1101
    """A format 5 branch. This supports new features over plan branches.
 
1102
 
 
1103
    It has support for a master_branch which is the data for bound branches.
 
1104
    """
 
1105
 
 
1106
    def __init__(self,
 
1107
                 _format,
 
1108
                 _control_files,
 
1109
                 a_bzrdir,
 
1110
                 _repository):
 
1111
        super(BzrBranch5, self).__init__(_format=_format,
 
1112
                                         _control_files=_control_files,
 
1113
                                         a_bzrdir=a_bzrdir,
 
1114
                                         _repository=_repository)
 
1115
        
 
1116
    @needs_write_lock
 
1117
    def pull(self, source, overwrite=False, stop_revision=None):
 
1118
        """Updates branch.pull to be bound branch aware."""
 
1119
        bound_location = self.get_bound_location()
 
1120
        if source.base != bound_location:
 
1121
            # not pulling from master, so we need to update master.
 
1122
            master_branch = self.get_master_branch()
 
1123
            if master_branch:
 
1124
                master_branch.pull(source)
 
1125
                source = master_branch
 
1126
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
1127
 
 
1128
    def get_bound_location(self):
1046
1129
        try:
1047
 
            idx = history.index(revision_id)
1048
 
        except ValueError:
1049
 
            raise InvalidRevisionId(revision_id=revision, branch=self)
1050
 
        return history[:idx+1]
 
1130
            return self.control_files.get_utf8('bound').read()[:-1]
 
1131
        except errors.NoSuchFile:
 
1132
            return None
1051
1133
 
1052
1134
    @needs_read_lock
1053
 
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
1054
 
        # prevent leakage
1055
 
        from bzrlib.workingtree import WorkingTree
1056
 
        assert isinstance(to_location, basestring)
1057
 
        if basis_branch is not None:
1058
 
            note("basis_branch is not supported for fast weave copy yet.")
1059
 
 
1060
 
        history = self._get_truncated_history(revision)
1061
 
        if not bzrlib.osutils.lexists(to_location):
1062
 
            os.mkdir(to_location)
1063
 
        branch_to = Branch.initialize(to_location)
1064
 
        mutter("copy branch from %s to %s", self, branch_to)
1065
 
 
1066
 
        self.repository.copy(branch_to.repository)
 
1135
    def get_master_branch(self):
 
1136
        """Return the branch we are bound to.
1067
1137
        
1068
 
        # must be done *after* history is copied across
1069
 
        # FIXME duplicate code with base .clone().
1070
 
        # .. would template method be useful here?  RBC 20051207
1071
 
        branch_to.set_parent(self.base)
1072
 
        branch_to.append_revision(*history)
1073
 
        # FIXME: this should be in workingtree.clone
1074
 
        WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
1075
 
        mutter("copied")
1076
 
        return branch_to
1077
 
 
1078
 
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
1079
 
        print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
1080
 
        if to_branch_type is None:
1081
 
            to_branch_type = BzrBranch
1082
 
 
1083
 
        if to_branch_type == BzrBranch \
1084
 
            and self.repository.weave_store.listable() \
1085
 
            and self.repository.revision_store.listable():
1086
 
            return self._clone_weave(to_location, revision, basis_branch)
1087
 
 
1088
 
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1089
 
 
1090
 
    def fileid_involved_between_revs(self, from_revid, to_revid):
1091
 
        """Find file_id(s) which are involved in the changes between revisions.
1092
 
 
1093
 
        This determines the set of revisions which are involved, and then
1094
 
        finds all file ids affected by those revisions.
1095
 
        """
1096
 
        # TODO: jam 20060119 This code assumes that w.inclusions will
1097
 
        #       always be correct. But because of the presence of ghosts
1098
 
        #       it is possible to be wrong.
1099
 
        #       One specific example from Robert Collins:
1100
 
        #       Two branches, with revisions ABC, and AD
1101
 
        #       C is a ghost merge of D.
1102
 
        #       Inclusions doesn't recognize D as an ancestor.
1103
 
        #       If D is ever merged in the future, the weave
1104
 
        #       won't be fixed, because AD never saw revision C
1105
 
        #       to cause a conflict which would force a reweave.
1106
 
        w = self.repository.get_inventory_weave()
1107
 
        from_set = set(w.inclusions([w.lookup(from_revid)]))
1108
 
        to_set = set(w.inclusions([w.lookup(to_revid)]))
1109
 
        included = to_set.difference(from_set)
1110
 
        changed = map(w.idx_to_name, included)
1111
 
        return self._fileid_involved_by_set(changed)
1112
 
 
1113
 
    def fileid_involved(self, last_revid=None):
1114
 
        """Find all file_ids modified in the ancestry of last_revid.
1115
 
 
1116
 
        :param last_revid: If None, last_revision() will be used.
1117
 
        """
1118
 
        w = self.repository.get_inventory_weave()
1119
 
        if not last_revid:
1120
 
            changed = set(w._names)
 
1138
        :return: Either a Branch, or None
 
1139
 
 
1140
        This could memoise the branch, but if thats done
 
1141
        it must be revalidated on each new lock.
 
1142
        So for now we just dont memoise it.
 
1143
        # RBC 20060304 review this decision.
 
1144
        """
 
1145
        bound_loc = self.get_bound_location()
 
1146
        if not bound_loc:
 
1147
            return None
 
1148
        try:
 
1149
            return Branch.open(bound_loc)
 
1150
        except (errors.NotBranchError, errors.ConnectionError), e:
 
1151
            raise errors.BoundBranchConnectionFailure(
 
1152
                    self, bound_loc, e)
 
1153
 
 
1154
    @needs_write_lock
 
1155
    def set_bound_location(self, location):
 
1156
        """Set the target where this branch is bound to.
 
1157
 
 
1158
        :param location: URL to the target branch
 
1159
        """
 
1160
        if location:
 
1161
            self.control_files.put_utf8('bound', location+'\n')
1121
1162
        else:
1122
 
            included = w.inclusions([w.lookup(last_revid)])
1123
 
            changed = map(w.idx_to_name, included)
1124
 
        return self._fileid_involved_by_set(changed)
1125
 
 
1126
 
    def fileid_involved_by_set(self, changes):
1127
 
        """Find all file_ids modified by the set of revisions passed in.
1128
 
 
1129
 
        :param changes: A set() of revision ids
 
1163
            try:
 
1164
                self.control_files._transport.delete('bound')
 
1165
            except NoSuchFile:
 
1166
                return False
 
1167
            return True
 
1168
 
 
1169
    @needs_write_lock
 
1170
    def bind(self, other):
 
1171
        """Bind the local branch the other branch.
 
1172
 
 
1173
        :param other: The branch to bind to
 
1174
        :type other: Branch
1130
1175
        """
1131
 
        # TODO: jam 20060119 This line does *nothing*, remove it.
1132
 
        #       or better yet, change _fileid_involved_by_set so
1133
 
        #       that it takes the inventory weave, rather than
1134
 
        #       pulling it out by itself.
1135
 
        w = self.repository.get_inventory_weave()
1136
 
        return self._fileid_involved_by_set(changes)
1137
 
 
1138
 
    def _fileid_involved_by_set(self, changes):
1139
 
        """Find the set of file-ids affected by the set of revisions.
1140
 
 
1141
 
        :param changes: A set() of revision ids.
1142
 
        :return: A set() of file ids.
 
1176
        # TODO: jam 20051230 Consider checking if the target is bound
 
1177
        #       It is debatable whether you should be able to bind to
 
1178
        #       a branch which is itself bound.
 
1179
        #       Committing is obviously forbidden,
 
1180
        #       but binding itself may not be.
 
1181
        #       Since we *have* to check at commit time, we don't
 
1182
        #       *need* to check here
 
1183
        self.pull(other)
 
1184
 
 
1185
        # we are now equal to or a suffix of other.
 
1186
 
 
1187
        # Since we have 'pulled' from the remote location,
 
1188
        # now we should try to pull in the opposite direction
 
1189
        # in case the local tree has more revisions than the
 
1190
        # remote one.
 
1191
        # There may be a different check you could do here
 
1192
        # rather than actually trying to install revisions remotely.
 
1193
        # TODO: capture an exception which indicates the remote branch
 
1194
        #       is not writeable. 
 
1195
        #       If it is up-to-date, this probably should not be a failure
1143
1196
        
1144
 
        This peaks at the Weave, interpreting each line, looking to
1145
 
        see if it mentions one of the revisions. And if so, includes
1146
 
        the file id mentioned.
1147
 
        This expects both the Weave format, and the serialization
1148
 
        to have a single line per file/directory, and to have
1149
 
        fileid="" and revision="" on that line.
 
1197
        # lock other for write so the revision-history syncing cannot race
 
1198
        other.lock_write()
 
1199
        try:
 
1200
            other.pull(self)
 
1201
            # if this does not error, other now has the same last rev we do
 
1202
            # it can only error if the pull from other was concurrent with
 
1203
            # a commit to other from someone else.
 
1204
 
 
1205
            # until we ditch revision-history, we need to sync them up:
 
1206
            self.set_revision_history(other.revision_history())
 
1207
            # now other and self are up to date with each other and have the
 
1208
            # same revision-history.
 
1209
        finally:
 
1210
            other.unlock()
 
1211
 
 
1212
        self.set_bound_location(other.base)
 
1213
 
 
1214
    @needs_write_lock
 
1215
    def unbind(self):
 
1216
        """If bound, unbind"""
 
1217
        return self.set_bound_location(None)
 
1218
 
 
1219
    @needs_write_lock
 
1220
    def update(self):
 
1221
        """Synchronise this branch with the master branch if any. 
 
1222
 
 
1223
        :return: None or the last_revision that was pivoted out during the
 
1224
                 update.
1150
1225
        """
1151
 
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
1152
 
                isinstance(self._branch_format, BzrBranchFormat6)), \
1153
 
            "fileid_involved only supported for branches which store inventory as xml"
1154
 
 
1155
 
        w = self.repository.get_inventory_weave()
1156
 
        file_ids = set()
1157
 
        for line in w._weave:
1158
 
 
1159
 
            # it is ugly, but it is due to the weave structure
1160
 
            if not isinstance(line, basestring): continue
1161
 
 
1162
 
            start = line.find('file_id="')+9
1163
 
            if start < 9: continue
1164
 
            end = line.find('"', start)
1165
 
            assert end>= 0
1166
 
            file_id = xml.sax.saxutils.unescape(line[start:end])
1167
 
 
1168
 
            # check if file_id is already present
1169
 
            if file_id in file_ids: continue
1170
 
 
1171
 
            start = line.find('revision="')+10
1172
 
            if start < 10: continue
1173
 
            end = line.find('"', start)
1174
 
            assert end>= 0
1175
 
            revision_id = xml.sax.saxutils.unescape(line[start:end])
1176
 
 
1177
 
            if revision_id in changes:
1178
 
                file_ids.add(file_id)
1179
 
 
1180
 
        return file_ids
1181
 
 
1182
 
 
1183
 
Branch.set_default_initializer(BzrBranch._initialize)
 
1226
        master = self.get_master_branch()
 
1227
        if master is not None:
 
1228
            old_tip = self.last_revision()
 
1229
            self.pull(master, overwrite=True)
 
1230
            if old_tip in self.repository.get_ancestry(self.last_revision()):
 
1231
                return None
 
1232
            return old_tip
 
1233
        return None
1184
1234
 
1185
1235
 
1186
1236
class BranchTestProviderAdapter(object):
1199
1249
    
1200
1250
    def adapt(self, test):
1201
1251
        result = TestSuite()
1202
 
        for format in self._formats:
 
1252
        for branch_format, bzrdir_format in self._formats:
1203
1253
            new_test = deepcopy(test)
1204
1254
            new_test.transport_server = self._transport_server
1205
1255
            new_test.transport_readonly_server = self._transport_readonly_server
1206
 
            new_test.branch_format = format
 
1256
            new_test.bzrdir_format = bzrdir_format
 
1257
            new_test.branch_format = branch_format
1207
1258
            def make_new_test_id():
1208
 
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1259
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1209
1260
                return lambda: new_id
1210
1261
            new_test.id = make_new_test_id()
1211
1262
            result.addTest(new_test)
1212
1263
        return result
1213
1264
 
1214
1265
 
1215
 
class ScratchBranch(BzrBranch):
1216
 
    """Special test class: a branch that cleans up after itself.
1217
 
 
1218
 
    >>> b = ScratchBranch()
1219
 
    >>> isdir(b.base)
1220
 
    True
1221
 
    >>> bd = b.base
1222
 
    >>> b._transport.__del__()
1223
 
    >>> isdir(bd)
1224
 
    False
1225
 
    """
1226
 
 
1227
 
    def __init__(self, files=[], dirs=[], transport=None):
1228
 
        """Make a test branch.
1229
 
 
1230
 
        This creates a temporary directory and runs init-tree in it.
1231
 
 
1232
 
        If any files are listed, they are created in the working copy.
1233
 
        """
1234
 
        if transport is None:
1235
 
            transport = bzrlib.transport.local.ScratchTransport()
1236
 
            # local import for scope restriction
1237
 
            from bzrlib.workingtree import WorkingTree
1238
 
            WorkingTree.create_standalone(transport.base)
1239
 
            super(ScratchBranch, self).__init__(transport)
1240
 
        else:
1241
 
            super(ScratchBranch, self).__init__(transport)
1242
 
 
1243
 
        # BzrBranch creates a clone to .bzr and then forgets about the
1244
 
        # original transport. A ScratchTransport() deletes itself and
1245
 
        # everything underneath it when it goes away, so we need to
1246
 
        # grab a local copy to prevent that from happening
1247
 
        self._transport = transport
1248
 
 
1249
 
        for d in dirs:
1250
 
            self._transport.mkdir(d)
1251
 
            
1252
 
        for f in files:
1253
 
            self._transport.put(f, 'content of %s' % f)
1254
 
 
1255
 
    def clone(self):
1256
 
        """
1257
 
        >>> orig = ScratchBranch(files=["file1", "file2"])
1258
 
        >>> os.listdir(orig.base)
1259
 
        [u'.bzr', u'file1', u'file2']
1260
 
        >>> clone = orig.clone()
1261
 
        >>> if os.name != 'nt':
1262
 
        ...   os.path.samefile(orig.base, clone.base)
1263
 
        ... else:
1264
 
        ...   orig.base == clone.base
1265
 
        ...
1266
 
        False
1267
 
        >>> os.listdir(clone.base)
1268
 
        [u'.bzr', u'file1', u'file2']
1269
 
        """
1270
 
        from shutil import copytree
1271
 
        from bzrlib.osutils import mkdtemp
1272
 
        base = mkdtemp()
1273
 
        os.rmdir(base)
1274
 
        copytree(self.base, base, symlinks=True)
1275
 
        return ScratchBranch(
1276
 
            transport=bzrlib.transport.local.ScratchTransport(base))
1277
 
    
1278
 
 
1279
1266
######################################################################
1280
1267
# predicates
1281
1268
 
1282
1269
 
1283
 
def is_control_file(filename):
1284
 
    ## FIXME: better check
1285
 
    filename = normpath(filename)
1286
 
    while filename != '':
1287
 
        head, tail = os.path.split(filename)
1288
 
        ## mutter('check %r for control file' % ((head, tail),))
1289
 
        if tail == bzrlib.BZRDIR:
1290
 
            return True
1291
 
        if filename == head:
1292
 
            break
1293
 
        filename = head
1294
 
    return False
 
1270
@deprecated_function(zero_eight)
 
1271
def ScratchBranch(*args, **kwargs):
 
1272
    """See bzrlib.bzrdir.ScratchDir."""
 
1273
    d = ScratchDir(*args, **kwargs)
 
1274
    return d.open_branch()
 
1275
 
 
1276
 
 
1277
@deprecated_function(zero_eight)
 
1278
def is_control_file(*args, **kwargs):
 
1279
    """See bzrlib.workingtree.is_control_file."""
 
1280
    return bzrlib.workingtree.is_control_file(*args, **kwargs)