/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2006-05-04 11:53:51 UTC
  • mto: (1697.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 1701.
  • Revision ID: robertc@robertcollins.net-20060504115351-79122d06d443d4a8
Teach Branch about break_lock.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
from copy import deepcopy
 
19
from cStringIO import StringIO
 
20
import errno
 
21
import os
 
22
import shutil
 
23
import sys
 
24
from unittest import TestSuite
 
25
from warnings import warn
 
26
 
 
27
import bzrlib
 
28
import bzrlib.bzrdir as bzrdir
 
29
from bzrlib.config import TreeConfig
 
30
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
31
from bzrlib.delta import compare_trees
 
32
import bzrlib.errors as errors
 
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
35
                           DivergedBranches, LockError,
 
36
                           UninitializableFormat,
 
37
                           UnlistableStore,
 
38
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
39
                           NoWorkingTree)
 
40
import bzrlib.inventory as inventory
 
41
from bzrlib.inventory import Inventory
 
42
from bzrlib.lockable_files import LockableFiles, TransportLock
 
43
from bzrlib.lockdir import LockDir
 
44
from bzrlib.osutils import (isdir, quotefn,
 
45
                            rename, splitpath, sha_file,
 
46
                            file_kind, abspath, normpath, pathjoin,
 
47
                            safe_unicode,
 
48
                            )
 
49
from bzrlib.textui import show_status
 
50
from bzrlib.trace import mutter, note
 
51
from bzrlib.tree import EmptyTree, RevisionTree
 
52
from bzrlib.repository import Repository
 
53
from bzrlib.revision import (
 
54
                             get_intervening_revisions,
 
55
                             is_ancestor,
 
56
                             NULL_REVISION,
 
57
                             Revision,
 
58
                             )
 
59
from bzrlib.store import copy_all
 
60
from bzrlib.symbol_versioning import *
 
61
import bzrlib.transactions as transactions
 
62
from bzrlib.transport import Transport, get_transport
 
63
from bzrlib.tree import EmptyTree, RevisionTree
 
64
import bzrlib.ui
 
65
import bzrlib.xml5
 
66
 
 
67
 
 
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
69
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
70
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
71
 
 
72
 
 
73
# TODO: Maybe include checks for common corruption of newlines, etc?
 
74
 
 
75
# TODO: Some operations like log might retrieve the same revisions
 
76
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
77
# cache in memory to make this faster.  In general anything can be
 
78
# cached in memory between lock and unlock operations. .. nb thats
 
79
# what the transaction identity map provides
 
80
 
 
81
 
 
82
######################################################################
 
83
# branch objects
 
84
 
 
85
class Branch(object):
 
86
    """Branch holding a history of revisions.
 
87
 
 
88
    base
 
89
        Base directory/url of the branch.
 
90
    """
 
91
    # this is really an instance variable - FIXME move it there
 
92
    # - RBC 20060112
 
93
    base = None
 
94
 
 
95
    def __init__(self, *ignored, **ignored_too):
 
96
        raise NotImplementedError('The Branch class is abstract')
 
97
 
 
98
    def break_lock(self):
 
99
        """Break a lock if one is present from another instance.
 
100
 
 
101
        Uses the ui factory to ask for confirmation if the lock may be from
 
102
        an active process.
 
103
 
 
104
        This will probe the repository for its lock as well.
 
105
        """
 
106
        self.control_files.break_lock()
 
107
        self.repository.break_lock()
 
108
 
 
109
    @staticmethod
 
110
    @deprecated_method(zero_eight)
 
111
    def open_downlevel(base):
 
112
        """Open a branch which may be of an old format."""
 
113
        return Branch.open(base, _unsupported=True)
 
114
        
 
115
    @staticmethod
 
116
    def open(base, _unsupported=False):
 
117
        """Open the repository rooted at base.
 
118
 
 
119
        For instance, if the repository is at URL/.bzr/repository,
 
120
        Repository.open(URL) -> a Repository instance.
 
121
        """
 
122
        control = bzrdir.BzrDir.open(base, _unsupported)
 
123
        return control.open_branch(_unsupported)
 
124
 
 
125
    @staticmethod
 
126
    def open_containing(url):
 
127
        """Open an existing branch which contains url.
 
128
        
 
129
        This probes for a branch at url, and searches upwards from there.
 
130
 
 
131
        Basically we keep looking up until we find the control directory or
 
132
        run into the root.  If there isn't one, raises NotBranchError.
 
133
        If there is one and it is either an unrecognised format or an unsupported 
 
134
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
135
        If there is one, it is returned, along with the unused portion of url.
 
136
        """
 
137
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
138
        return control.open_branch(), relpath
 
139
 
 
140
    @staticmethod
 
141
    @deprecated_function(zero_eight)
 
142
    def initialize(base):
 
143
        """Create a new working tree and branch, rooted at 'base' (url)
 
144
 
 
145
        NOTE: This will soon be deprecated in favour of creation
 
146
        through a BzrDir.
 
147
        """
 
148
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
 
149
 
 
150
    def setup_caching(self, cache_root):
 
151
        """Subclasses that care about caching should override this, and set
 
152
        up cached stores located under cache_root.
 
153
        """
 
154
        # seems to be unused, 2006-01-13 mbp
 
155
        warn('%s is deprecated' % self.setup_caching)
 
156
        self.cache_root = cache_root
 
157
 
 
158
    def _get_nick(self):
 
159
        cfg = self.tree_config()
 
160
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
 
161
 
 
162
    def _set_nick(self, nick):
 
163
        cfg = self.tree_config()
 
164
        cfg.set_option(nick, "nickname")
 
165
        assert cfg.get_option("nickname") == nick
 
166
 
 
167
    nick = property(_get_nick, _set_nick)
 
168
        
 
169
    def lock_write(self):
 
170
        raise NotImplementedError('lock_write is abstract')
 
171
        
 
172
    def lock_read(self):
 
173
        raise NotImplementedError('lock_read is abstract')
 
174
 
 
175
    def unlock(self):
 
176
        raise NotImplementedError('unlock is abstract')
 
177
 
 
178
    def peek_lock_mode(self):
 
179
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
180
        raise NotImplementedError(self.peek_lock_mode)
 
181
 
 
182
    def abspath(self, name):
 
183
        """Return absolute filename for something in the branch
 
184
        
 
185
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
186
        method and not a tree method.
 
187
        """
 
188
        raise NotImplementedError('abspath is abstract')
 
189
 
 
190
    def bind(self, other):
 
191
        """Bind the local branch the other branch.
 
192
 
 
193
        :param other: The branch to bind to
 
194
        :type other: Branch
 
195
        """
 
196
        raise errors.UpgradeRequired(self.base)
 
197
 
 
198
    @needs_write_lock
 
199
    def fetch(self, from_branch, last_revision=None, pb=None):
 
200
        """Copy revisions from from_branch into this branch.
 
201
 
 
202
        :param from_branch: Where to copy from.
 
203
        :param last_revision: What revision to stop at (None for at the end
 
204
                              of the branch.
 
205
        :param pb: An optional progress bar to use.
 
206
 
 
207
        Returns the copied revision count and the failed revisions in a tuple:
 
208
        (copied, failures).
 
209
        """
 
210
        if self.base == from_branch.base:
 
211
            return (0, [])
 
212
        if pb is None:
 
213
            nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
214
            pb = nested_pb
 
215
        else:
 
216
            nested_pb = None
 
217
 
 
218
        from_branch.lock_read()
 
219
        try:
 
220
            if last_revision is None:
 
221
                pb.update('get source history')
 
222
                from_history = from_branch.revision_history()
 
223
                if from_history:
 
224
                    last_revision = from_history[-1]
 
225
                else:
 
226
                    # no history in the source branch
 
227
                    last_revision = NULL_REVISION
 
228
            return self.repository.fetch(from_branch.repository,
 
229
                                         revision_id=last_revision,
 
230
                                         pb=nested_pb)
 
231
        finally:
 
232
            if nested_pb is not None:
 
233
                nested_pb.finished()
 
234
            from_branch.unlock()
 
235
 
 
236
    def get_bound_location(self):
 
237
        """Return the URL of the branch we are bound to.
 
238
 
 
239
        Older format branches cannot bind, please be sure to use a metadir
 
240
        branch.
 
241
        """
 
242
        return None
 
243
 
 
244
    def get_master_branch(self):
 
245
        """Return the branch we are bound to.
 
246
        
 
247
        :return: Either a Branch, or None
 
248
        """
 
249
        return None
 
250
 
 
251
    def get_root_id(self):
 
252
        """Return the id of this branches root"""
 
253
        raise NotImplementedError('get_root_id is abstract')
 
254
 
 
255
    def print_file(self, file, revision_id):
 
256
        """Print `file` to stdout."""
 
257
        raise NotImplementedError('print_file is abstract')
 
258
 
 
259
    def append_revision(self, *revision_ids):
 
260
        raise NotImplementedError('append_revision is abstract')
 
261
 
 
262
    def set_revision_history(self, rev_history):
 
263
        raise NotImplementedError('set_revision_history is abstract')
 
264
 
 
265
    def revision_history(self):
 
266
        """Return sequence of revision hashes on to this branch."""
 
267
        raise NotImplementedError('revision_history is abstract')
 
268
 
 
269
    def revno(self):
 
270
        """Return current revision number for this branch.
 
271
 
 
272
        That is equivalent to the number of revisions committed to
 
273
        this branch.
 
274
        """
 
275
        return len(self.revision_history())
 
276
 
 
277
    def unbind(self):
 
278
        """Older format branches cannot bind or unbind."""
 
279
        raise errors.UpgradeRequired(self.base)
 
280
 
 
281
    def last_revision(self):
 
282
        """Return last patch hash, or None if no history."""
 
283
        ph = self.revision_history()
 
284
        if ph:
 
285
            return ph[-1]
 
286
        else:
 
287
            return None
 
288
 
 
289
    def missing_revisions(self, other, stop_revision=None):
 
290
        """Return a list of new revisions that would perfectly fit.
 
291
        
 
292
        If self and other have not diverged, return a list of the revisions
 
293
        present in other, but missing from self.
 
294
 
 
295
        >>> from bzrlib.workingtree import WorkingTree
 
296
        >>> bzrlib.trace.silent = True
 
297
        >>> d1 = bzrdir.ScratchDir()
 
298
        >>> br1 = d1.open_branch()
 
299
        >>> wt1 = d1.open_workingtree()
 
300
        >>> d2 = bzrdir.ScratchDir()
 
301
        >>> br2 = d2.open_branch()
 
302
        >>> wt2 = d2.open_workingtree()
 
303
        >>> br1.missing_revisions(br2)
 
304
        []
 
305
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
 
306
        >>> br1.missing_revisions(br2)
 
307
        [u'REVISION-ID-1']
 
308
        >>> br2.missing_revisions(br1)
 
309
        []
 
310
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
 
311
        >>> br1.missing_revisions(br2)
 
312
        []
 
313
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
 
314
        >>> br1.missing_revisions(br2)
 
315
        [u'REVISION-ID-2A']
 
316
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
 
317
        >>> br1.missing_revisions(br2)
 
318
        Traceback (most recent call last):
 
319
        DivergedBranches: These branches have diverged.  Try merge.
 
320
        """
 
321
        self_history = self.revision_history()
 
322
        self_len = len(self_history)
 
323
        other_history = other.revision_history()
 
324
        other_len = len(other_history)
 
325
        common_index = min(self_len, other_len) -1
 
326
        if common_index >= 0 and \
 
327
            self_history[common_index] != other_history[common_index]:
 
328
            raise DivergedBranches(self, other)
 
329
 
 
330
        if stop_revision is None:
 
331
            stop_revision = other_len
 
332
        else:
 
333
            assert isinstance(stop_revision, int)
 
334
            if stop_revision > other_len:
 
335
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
336
        return other_history[self_len:stop_revision]
 
337
 
 
338
    def update_revisions(self, other, stop_revision=None):
 
339
        """Pull in new perfect-fit revisions.
 
340
 
 
341
        :param other: Another Branch to pull from
 
342
        :param stop_revision: Updated until the given revision
 
343
        :return: None
 
344
        """
 
345
        raise NotImplementedError('update_revisions is abstract')
 
346
 
 
347
    def revision_id_to_revno(self, revision_id):
 
348
        """Given a revision id, return its revno"""
 
349
        if revision_id is None:
 
350
            return 0
 
351
        history = self.revision_history()
 
352
        try:
 
353
            return history.index(revision_id) + 1
 
354
        except ValueError:
 
355
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
356
 
 
357
    def get_rev_id(self, revno, history=None):
 
358
        """Find the revision id of the specified revno."""
 
359
        if revno == 0:
 
360
            return None
 
361
        if history is None:
 
362
            history = self.revision_history()
 
363
        elif revno <= 0 or revno > len(history):
 
364
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
365
        return history[revno - 1]
 
366
 
 
367
    def pull(self, source, overwrite=False, stop_revision=None):
 
368
        raise NotImplementedError('pull is abstract')
 
369
 
 
370
    def basis_tree(self):
 
371
        """Return `Tree` object for last revision.
 
372
 
 
373
        If there are no revisions yet, return an `EmptyTree`.
 
374
        """
 
375
        return self.repository.revision_tree(self.last_revision())
 
376
 
 
377
    def rename_one(self, from_rel, to_rel):
 
378
        """Rename one file.
 
379
 
 
380
        This can change the directory or the filename or both.
 
381
        """
 
382
        raise NotImplementedError('rename_one is abstract')
 
383
 
 
384
    def move(self, from_paths, to_name):
 
385
        """Rename files.
 
386
 
 
387
        to_name must exist as a versioned directory.
 
388
 
 
389
        If to_name exists and is a directory, the files are moved into
 
390
        it, keeping their old names.  If it is a directory, 
 
391
 
 
392
        Note that to_name is only the last component of the new name;
 
393
        this doesn't change the directory.
 
394
 
 
395
        This returns a list of (from_path, to_path) pairs for each
 
396
        entry that is moved.
 
397
        """
 
398
        raise NotImplementedError('move is abstract')
 
399
 
 
400
    def get_parent(self):
 
401
        """Return the parent location of the branch.
 
402
 
 
403
        This is the default location for push/pull/missing.  The usual
 
404
        pattern is that the user can override it by specifying a
 
405
        location.
 
406
        """
 
407
        raise NotImplementedError('get_parent is abstract')
 
408
 
 
409
    def get_push_location(self):
 
410
        """Return the None or the location to push this branch to."""
 
411
        raise NotImplementedError('get_push_location is abstract')
 
412
 
 
413
    def set_push_location(self, location):
 
414
        """Set a new push location for this branch."""
 
415
        raise NotImplementedError('set_push_location is abstract')
 
416
 
 
417
    def set_parent(self, url):
 
418
        raise NotImplementedError('set_parent is abstract')
 
419
 
 
420
    @needs_write_lock
 
421
    def update(self):
 
422
        """Synchronise this branch with the master branch if any. 
 
423
 
 
424
        :return: None or the last_revision pivoted out during the update.
 
425
        """
 
426
        return None
 
427
 
 
428
    def check_revno(self, revno):
 
429
        """\
 
430
        Check whether a revno corresponds to any revision.
 
431
        Zero (the NULL revision) is considered valid.
 
432
        """
 
433
        if revno != 0:
 
434
            self.check_real_revno(revno)
 
435
            
 
436
    def check_real_revno(self, revno):
 
437
        """\
 
438
        Check whether a revno corresponds to a real revision.
 
439
        Zero (the NULL revision) is considered invalid
 
440
        """
 
441
        if revno < 1 or revno > self.revno():
 
442
            raise InvalidRevisionNumber(revno)
 
443
 
 
444
    @needs_read_lock
 
445
    def clone(self, *args, **kwargs):
 
446
        """Clone this branch into to_bzrdir preserving all semantic values.
 
447
        
 
448
        revision_id: if not None, the revision history in the new branch will
 
449
                     be truncated to end with revision_id.
 
450
        """
 
451
        # for API compatability, until 0.8 releases we provide the old api:
 
452
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
453
        # after 0.8 releases, the *args and **kwargs should be changed:
 
454
        # def clone(self, to_bzrdir, revision_id=None):
 
455
        if (kwargs.get('to_location', None) or
 
456
            kwargs.get('revision', None) or
 
457
            kwargs.get('basis_branch', None) or
 
458
            (len(args) and isinstance(args[0], basestring))):
 
459
            # backwards compatability api:
 
460
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
461
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
462
            # get basis_branch
 
463
            if len(args) > 2:
 
464
                basis_branch = args[2]
 
465
            else:
 
466
                basis_branch = kwargs.get('basis_branch', None)
 
467
            if basis_branch:
 
468
                basis = basis_branch.bzrdir
 
469
            else:
 
470
                basis = None
 
471
            # get revision
 
472
            if len(args) > 1:
 
473
                revision_id = args[1]
 
474
            else:
 
475
                revision_id = kwargs.get('revision', None)
 
476
            # get location
 
477
            if len(args):
 
478
                url = args[0]
 
479
            else:
 
480
                # no default to raise if not provided.
 
481
                url = kwargs.get('to_location')
 
482
            return self.bzrdir.clone(url,
 
483
                                     revision_id=revision_id,
 
484
                                     basis=basis).open_branch()
 
485
        # new cleaner api.
 
486
        # generate args by hand 
 
487
        if len(args) > 1:
 
488
            revision_id = args[1]
 
489
        else:
 
490
            revision_id = kwargs.get('revision_id', None)
 
491
        if len(args):
 
492
            to_bzrdir = args[0]
 
493
        else:
 
494
            # no default to raise if not provided.
 
495
            to_bzrdir = kwargs.get('to_bzrdir')
 
496
        result = self._format.initialize(to_bzrdir)
 
497
        self.copy_content_into(result, revision_id=revision_id)
 
498
        return  result
 
499
 
 
500
    @needs_read_lock
 
501
    def sprout(self, to_bzrdir, revision_id=None):
 
502
        """Create a new line of development from the branch, into to_bzrdir.
 
503
        
 
504
        revision_id: if not None, the revision history in the new branch will
 
505
                     be truncated to end with revision_id.
 
506
        """
 
507
        result = self._format.initialize(to_bzrdir)
 
508
        self.copy_content_into(result, revision_id=revision_id)
 
509
        result.set_parent(self.bzrdir.root_transport.base)
 
510
        return result
 
511
 
 
512
    @needs_read_lock
 
513
    def copy_content_into(self, destination, revision_id=None):
 
514
        """Copy the content of self into destination.
 
515
 
 
516
        revision_id: if not None, the revision history in the new branch will
 
517
                     be truncated to end with revision_id.
 
518
        """
 
519
        new_history = self.revision_history()
 
520
        if revision_id is not None:
 
521
            try:
 
522
                new_history = new_history[:new_history.index(revision_id) + 1]
 
523
            except ValueError:
 
524
                rev = self.repository.get_revision(revision_id)
 
525
                new_history = rev.get_history(self.repository)[1:]
 
526
        destination.set_revision_history(new_history)
 
527
        parent = self.get_parent()
 
528
        if parent:
 
529
            destination.set_parent(parent)
 
530
 
 
531
 
 
532
class BranchFormat(object):
 
533
    """An encapsulation of the initialization and open routines for a format.
 
534
 
 
535
    Formats provide three things:
 
536
     * An initialization routine,
 
537
     * a format string,
 
538
     * an open routine.
 
539
 
 
540
    Formats are placed in an dict by their format string for reference 
 
541
    during branch opening. Its not required that these be instances, they
 
542
    can be classes themselves with class methods - it simply depends on 
 
543
    whether state is needed for a given format or not.
 
544
 
 
545
    Once a format is deprecated, just deprecate the initialize and open
 
546
    methods on the format class. Do not deprecate the object, as the 
 
547
    object will be created every time regardless.
 
548
    """
 
549
 
 
550
    _default_format = None
 
551
    """The default format used for new branches."""
 
552
 
 
553
    _formats = {}
 
554
    """The known formats."""
 
555
 
 
556
    @classmethod
 
557
    def find_format(klass, a_bzrdir):
 
558
        """Return the format for the branch object in a_bzrdir."""
 
559
        try:
 
560
            transport = a_bzrdir.get_branch_transport(None)
 
561
            format_string = transport.get("format").read()
 
562
            return klass._formats[format_string]
 
563
        except NoSuchFile:
 
564
            raise NotBranchError(path=transport.base)
 
565
        except KeyError:
 
566
            raise errors.UnknownFormatError(format_string)
 
567
 
 
568
    @classmethod
 
569
    def get_default_format(klass):
 
570
        """Return the current default format."""
 
571
        return klass._default_format
 
572
 
 
573
    def get_format_string(self):
 
574
        """Return the ASCII format string that identifies this format."""
 
575
        raise NotImplementedError(self.get_format_string)
 
576
 
 
577
    def get_format_description(self):
 
578
        """Return the short format description for this format."""
 
579
        raise NotImplementedError(self.get_format_string)
 
580
 
 
581
    def initialize(self, a_bzrdir):
 
582
        """Create a branch of this format in a_bzrdir."""
 
583
        raise NotImplementedError(self.initialized)
 
584
 
 
585
    def is_supported(self):
 
586
        """Is this format supported?
 
587
 
 
588
        Supported formats can be initialized and opened.
 
589
        Unsupported formats may not support initialization or committing or 
 
590
        some other features depending on the reason for not being supported.
 
591
        """
 
592
        return True
 
593
 
 
594
    def open(self, a_bzrdir, _found=False):
 
595
        """Return the branch object for a_bzrdir
 
596
 
 
597
        _found is a private parameter, do not use it. It is used to indicate
 
598
               if format probing has already be done.
 
599
        """
 
600
        raise NotImplementedError(self.open)
 
601
 
 
602
    @classmethod
 
603
    def register_format(klass, format):
 
604
        klass._formats[format.get_format_string()] = format
 
605
 
 
606
    @classmethod
 
607
    def set_default_format(klass, format):
 
608
        klass._default_format = format
 
609
 
 
610
    @classmethod
 
611
    def unregister_format(klass, format):
 
612
        assert klass._formats[format.get_format_string()] is format
 
613
        del klass._formats[format.get_format_string()]
 
614
 
 
615
    def __str__(self):
 
616
        return self.get_format_string().rstrip()
 
617
 
 
618
 
 
619
class BzrBranchFormat4(BranchFormat):
 
620
    """Bzr branch format 4.
 
621
 
 
622
    This format has:
 
623
     - a revision-history file.
 
624
     - a branch-lock lock file [ to be shared with the bzrdir ]
 
625
    """
 
626
 
 
627
    def get_format_description(self):
 
628
        """See BranchFormat.get_format_description()."""
 
629
        return "Branch format 4"
 
630
 
 
631
    def initialize(self, a_bzrdir):
 
632
        """Create a branch of this format in a_bzrdir."""
 
633
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
634
        branch_transport = a_bzrdir.get_branch_transport(self)
 
635
        utf8_files = [('revision-history', ''),
 
636
                      ('branch-name', ''),
 
637
                      ]
 
638
        control_files = LockableFiles(branch_transport, 'branch-lock',
 
639
                                      TransportLock)
 
640
        control_files.create_lock()
 
641
        control_files.lock_write()
 
642
        try:
 
643
            for file, content in utf8_files:
 
644
                control_files.put_utf8(file, content)
 
645
        finally:
 
646
            control_files.unlock()
 
647
        return self.open(a_bzrdir, _found=True)
 
648
 
 
649
    def __init__(self):
 
650
        super(BzrBranchFormat4, self).__init__()
 
651
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
652
 
 
653
    def open(self, a_bzrdir, _found=False):
 
654
        """Return the branch object for a_bzrdir
 
655
 
 
656
        _found is a private parameter, do not use it. It is used to indicate
 
657
               if format probing has already be done.
 
658
        """
 
659
        if not _found:
 
660
            # we are being called directly and must probe.
 
661
            raise NotImplementedError
 
662
        return BzrBranch(_format=self,
 
663
                         _control_files=a_bzrdir._control_files,
 
664
                         a_bzrdir=a_bzrdir,
 
665
                         _repository=a_bzrdir.open_repository())
 
666
 
 
667
    def __str__(self):
 
668
        return "Bazaar-NG branch format 4"
 
669
 
 
670
 
 
671
class BzrBranchFormat5(BranchFormat):
 
672
    """Bzr branch format 5.
 
673
 
 
674
    This format has:
 
675
     - a revision-history file.
 
676
     - a format string
 
677
     - a lock dir guarding the branch itself
 
678
     - all of this stored in a branch/ subdirectory
 
679
     - works with shared repositories.
 
680
 
 
681
    This format is new in bzr 0.8.
 
682
    """
 
683
 
 
684
    def get_format_string(self):
 
685
        """See BranchFormat.get_format_string()."""
 
686
        return "Bazaar-NG branch format 5\n"
 
687
 
 
688
    def get_format_description(self):
 
689
        """See BranchFormat.get_format_description()."""
 
690
        return "Branch format 5"
 
691
        
 
692
    def initialize(self, a_bzrdir):
 
693
        """Create a branch of this format in a_bzrdir."""
 
694
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
695
        branch_transport = a_bzrdir.get_branch_transport(self)
 
696
        utf8_files = [('revision-history', ''),
 
697
                      ('branch-name', ''),
 
698
                      ]
 
699
        control_files = LockableFiles(branch_transport, 'lock', LockDir)
 
700
        control_files.create_lock()
 
701
        control_files.lock_write()
 
702
        control_files.put_utf8('format', self.get_format_string())
 
703
        try:
 
704
            for file, content in utf8_files:
 
705
                control_files.put_utf8(file, content)
 
706
        finally:
 
707
            control_files.unlock()
 
708
        return self.open(a_bzrdir, _found=True, )
 
709
 
 
710
    def __init__(self):
 
711
        super(BzrBranchFormat5, self).__init__()
 
712
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
713
 
 
714
    def open(self, a_bzrdir, _found=False):
 
715
        """Return the branch object for a_bzrdir
 
716
 
 
717
        _found is a private parameter, do not use it. It is used to indicate
 
718
               if format probing has already be done.
 
719
        """
 
720
        if not _found:
 
721
            format = BranchFormat.find_format(a_bzrdir)
 
722
            assert format.__class__ == self.__class__
 
723
        transport = a_bzrdir.get_branch_transport(None)
 
724
        control_files = LockableFiles(transport, 'lock', LockDir)
 
725
        return BzrBranch5(_format=self,
 
726
                          _control_files=control_files,
 
727
                          a_bzrdir=a_bzrdir,
 
728
                          _repository=a_bzrdir.find_repository())
 
729
 
 
730
    def __str__(self):
 
731
        return "Bazaar-NG Metadir branch format 5"
 
732
 
 
733
 
 
734
class BranchReferenceFormat(BranchFormat):
 
735
    """Bzr branch reference format.
 
736
 
 
737
    Branch references are used in implementing checkouts, they
 
738
    act as an alias to the real branch which is at some other url.
 
739
 
 
740
    This format has:
 
741
     - A location file
 
742
     - a format string
 
743
    """
 
744
 
 
745
    def get_format_string(self):
 
746
        """See BranchFormat.get_format_string()."""
 
747
        return "Bazaar-NG Branch Reference Format 1\n"
 
748
 
 
749
    def get_format_description(self):
 
750
        """See BranchFormat.get_format_description()."""
 
751
        return "Checkout reference format 1"
 
752
        
 
753
    def initialize(self, a_bzrdir, target_branch=None):
 
754
        """Create a branch of this format in a_bzrdir."""
 
755
        if target_branch is None:
 
756
            # this format does not implement branch itself, thus the implicit
 
757
            # creation contract must see it as uninitializable
 
758
            raise errors.UninitializableFormat(self)
 
759
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
760
        branch_transport = a_bzrdir.get_branch_transport(self)
 
761
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
762
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
763
        branch_transport.put('format', StringIO(self.get_format_string()))
 
764
        return self.open(a_bzrdir, _found=True)
 
765
 
 
766
    def __init__(self):
 
767
        super(BranchReferenceFormat, self).__init__()
 
768
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
769
 
 
770
    def _make_reference_clone_function(format, a_branch):
 
771
        """Create a clone() routine for a branch dynamically."""
 
772
        def clone(to_bzrdir, revision_id=None):
 
773
            """See Branch.clone()."""
 
774
            return format.initialize(to_bzrdir, a_branch)
 
775
            # cannot obey revision_id limits when cloning a reference ...
 
776
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
777
            # emit some sort of warning/error to the caller ?!
 
778
        return clone
 
779
 
 
780
    def open(self, a_bzrdir, _found=False):
 
781
        """Return the branch that the branch reference in a_bzrdir points at.
 
782
 
 
783
        _found is a private parameter, do not use it. It is used to indicate
 
784
               if format probing has already be done.
 
785
        """
 
786
        if not _found:
 
787
            format = BranchFormat.find_format(a_bzrdir)
 
788
            assert format.__class__ == self.__class__
 
789
        transport = a_bzrdir.get_branch_transport(None)
 
790
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
791
        result = real_bzrdir.open_branch()
 
792
        # this changes the behaviour of result.clone to create a new reference
 
793
        # rather than a copy of the content of the branch.
 
794
        # I did not use a proxy object because that needs much more extensive
 
795
        # testing, and we are only changing one behaviour at the moment.
 
796
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
797
        # then this should be refactored to introduce a tested proxy branch
 
798
        # and a subclass of that for use in overriding clone() and ....
 
799
        # - RBC 20060210
 
800
        result.clone = self._make_reference_clone_function(result)
 
801
        return result
 
802
 
 
803
 
 
804
# formats which have no format string are not discoverable
 
805
# and not independently creatable, so are not registered.
 
806
__default_format = BzrBranchFormat5()
 
807
BranchFormat.register_format(__default_format)
 
808
BranchFormat.register_format(BranchReferenceFormat())
 
809
BranchFormat.set_default_format(__default_format)
 
810
_legacy_formats = [BzrBranchFormat4(),
 
811
                   ]
 
812
 
 
813
class BzrBranch(Branch):
 
814
    """A branch stored in the actual filesystem.
 
815
 
 
816
    Note that it's "local" in the context of the filesystem; it doesn't
 
817
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
818
    it's writable, and can be accessed via the normal filesystem API.
 
819
    """
 
820
    
 
821
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
 
822
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
823
                 _control_files=None, a_bzrdir=None, _repository=None):
 
824
        """Create new branch object at a particular location.
 
825
 
 
826
        transport -- A Transport object, defining how to access files.
 
827
        
 
828
        init -- If True, create new control files in a previously
 
829
             unversioned directory.  If False, the branch must already
 
830
             be versioned.
 
831
 
 
832
        relax_version_check -- If true, the usual check for the branch
 
833
            version is not applied.  This is intended only for
 
834
            upgrade/recovery type use; it's not guaranteed that
 
835
            all operations will work on old format branches.
 
836
        """
 
837
        if a_bzrdir is None:
 
838
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
839
        else:
 
840
            self.bzrdir = a_bzrdir
 
841
        self._transport = self.bzrdir.transport.clone('..')
 
842
        self._base = self._transport.base
 
843
        self._format = _format
 
844
        if _control_files is None:
 
845
            raise BzrBadParameterMissing('_control_files')
 
846
        self.control_files = _control_files
 
847
        if deprecated_passed(init):
 
848
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
849
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
850
                 DeprecationWarning,
 
851
                 stacklevel=2)
 
852
            if init:
 
853
                # this is slower than before deprecation, oh well never mind.
 
854
                # -> its deprecated.
 
855
                self._initialize(transport.base)
 
856
        self._check_format(_format)
 
857
        if deprecated_passed(relax_version_check):
 
858
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
859
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
860
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
 
861
                 "open() method.",
 
862
                 DeprecationWarning,
 
863
                 stacklevel=2)
 
864
            if (not relax_version_check
 
865
                and not self._format.is_supported()):
 
866
                raise errors.UnsupportedFormatError(
 
867
                        'sorry, branch format %r not supported' % fmt,
 
868
                        ['use a different bzr version',
 
869
                         'or remove the .bzr directory'
 
870
                         ' and "bzr init" again'])
 
871
        if deprecated_passed(transport):
 
872
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
873
                 "parameter is deprecated as of bzr 0.8. "
 
874
                 "Please use Branch.open, or bzrdir.open_branch().",
 
875
                 DeprecationWarning,
 
876
                 stacklevel=2)
 
877
        self.repository = _repository
 
878
 
 
879
    def __str__(self):
 
880
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
881
 
 
882
    __repr__ = __str__
 
883
 
 
884
    def __del__(self):
 
885
        # TODO: It might be best to do this somewhere else,
 
886
        # but it is nice for a Branch object to automatically
 
887
        # cache it's information.
 
888
        # Alternatively, we could have the Transport objects cache requests
 
889
        # See the earlier discussion about how major objects (like Branch)
 
890
        # should never expect their __del__ function to run.
 
891
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
 
892
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
893
            try:
 
894
                shutil.rmtree(self.cache_root)
 
895
            except:
 
896
                pass
 
897
            self.cache_root = None
 
898
 
 
899
    def _get_base(self):
 
900
        return self._base
 
901
 
 
902
    base = property(_get_base, doc="The URL for the root of this branch.")
 
903
 
 
904
    def _finish_transaction(self):
 
905
        """Exit the current transaction."""
 
906
        return self.control_files._finish_transaction()
 
907
 
 
908
    def get_transaction(self):
 
909
        """Return the current active transaction.
 
910
 
 
911
        If no transaction is active, this returns a passthrough object
 
912
        for which all data is immediately flushed and no caching happens.
 
913
        """
 
914
        # this is an explicit function so that we can do tricky stuff
 
915
        # when the storage in rev_storage is elsewhere.
 
916
        # we probably need to hook the two 'lock a location' and 
 
917
        # 'have a transaction' together more delicately, so that
 
918
        # we can have two locks (branch and storage) and one transaction
 
919
        # ... and finishing the transaction unlocks both, but unlocking
 
920
        # does not. - RBC 20051121
 
921
        return self.control_files.get_transaction()
 
922
 
 
923
    def _set_transaction(self, transaction):
 
924
        """Set a new active transaction."""
 
925
        return self.control_files._set_transaction(transaction)
 
926
 
 
927
    def abspath(self, name):
 
928
        """See Branch.abspath."""
 
929
        return self.control_files._transport.abspath(name)
 
930
 
 
931
    def _check_format(self, format):
 
932
        """Identify the branch format if needed.
 
933
 
 
934
        The format is stored as a reference to the format object in
 
935
        self._format for code that needs to check it later.
 
936
 
 
937
        The format parameter is either None or the branch format class
 
938
        used to open this branch.
 
939
 
 
940
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
 
941
        """
 
942
        if format is None:
 
943
            format = BzrBranchFormat.find_format(self.bzrdir)
 
944
        self._format = format
 
945
        mutter("got branch format %s", self._format)
 
946
 
 
947
    @needs_read_lock
 
948
    def get_root_id(self):
 
949
        """See Branch.get_root_id."""
 
950
        tree = self.repository.revision_tree(self.last_revision())
 
951
        return tree.inventory.root.file_id
 
952
 
 
953
    def lock_write(self):
 
954
        # TODO: test for failed two phase locks. This is known broken.
 
955
        self.control_files.lock_write()
 
956
        self.repository.lock_write()
 
957
 
 
958
    def lock_read(self):
 
959
        # TODO: test for failed two phase locks. This is known broken.
 
960
        self.control_files.lock_read()
 
961
        self.repository.lock_read()
 
962
 
 
963
    def unlock(self):
 
964
        # TODO: test for failed two phase locks. This is known broken.
 
965
        try:
 
966
            self.repository.unlock()
 
967
        finally:
 
968
            self.control_files.unlock()
 
969
        
 
970
    def peek_lock_mode(self):
 
971
        if self.control_files._lock_count == 0:
 
972
            return None
 
973
        else:
 
974
            return self.control_files._lock_mode
 
975
 
 
976
    @needs_read_lock
 
977
    def print_file(self, file, revision_id):
 
978
        """See Branch.print_file."""
 
979
        return self.repository.print_file(file, revision_id)
 
980
 
 
981
    @needs_write_lock
 
982
    def append_revision(self, *revision_ids):
 
983
        """See Branch.append_revision."""
 
984
        for revision_id in revision_ids:
 
985
            mutter("add {%s} to revision-history" % revision_id)
 
986
        rev_history = self.revision_history()
 
987
        rev_history.extend(revision_ids)
 
988
        self.set_revision_history(rev_history)
 
989
 
 
990
    @needs_write_lock
 
991
    def set_revision_history(self, rev_history):
 
992
        """See Branch.set_revision_history."""
 
993
        self.control_files.put_utf8(
 
994
            'revision-history', '\n'.join(rev_history))
 
995
        transaction = self.get_transaction()
 
996
        history = transaction.map.find_revision_history()
 
997
        if history is not None:
 
998
            # update the revision history in the identity map.
 
999
            history[:] = list(rev_history)
 
1000
            # this call is disabled because revision_history is 
 
1001
            # not really an object yet, and the transaction is for objects.
 
1002
            # transaction.register_dirty(history)
 
1003
        else:
 
1004
            transaction.map.add_revision_history(rev_history)
 
1005
            # this call is disabled because revision_history is 
 
1006
            # not really an object yet, and the transaction is for objects.
 
1007
            # transaction.register_clean(history)
 
1008
 
 
1009
    def get_revision_delta(self, revno):
 
1010
        """Return the delta for one revision.
 
1011
 
 
1012
        The delta is relative to its mainline predecessor, or the
 
1013
        empty tree for revision 1.
 
1014
        """
 
1015
        assert isinstance(revno, int)
 
1016
        rh = self.revision_history()
 
1017
        if not (1 <= revno <= len(rh)):
 
1018
            raise InvalidRevisionNumber(revno)
 
1019
 
 
1020
        # revno is 1-based; list is 0-based
 
1021
 
 
1022
        new_tree = self.repository.revision_tree(rh[revno-1])
 
1023
        if revno == 1:
 
1024
            old_tree = EmptyTree()
 
1025
        else:
 
1026
            old_tree = self.repository.revision_tree(rh[revno-2])
 
1027
        return compare_trees(old_tree, new_tree)
 
1028
 
 
1029
    @needs_read_lock
 
1030
    def revision_history(self):
 
1031
        """See Branch.revision_history."""
 
1032
        transaction = self.get_transaction()
 
1033
        history = transaction.map.find_revision_history()
 
1034
        if history is not None:
 
1035
            mutter("cache hit for revision-history in %s", self)
 
1036
            return list(history)
 
1037
        history = [l.rstrip('\r\n') for l in
 
1038
                self.control_files.get_utf8('revision-history').readlines()]
 
1039
        transaction.map.add_revision_history(history)
 
1040
        # this call is disabled because revision_history is 
 
1041
        # not really an object yet, and the transaction is for objects.
 
1042
        # transaction.register_clean(history, precious=True)
 
1043
        return list(history)
 
1044
 
 
1045
    @needs_write_lock
 
1046
    def update_revisions(self, other, stop_revision=None):
 
1047
        """See Branch.update_revisions."""
 
1048
        other.lock_read()
 
1049
        try:
 
1050
            if stop_revision is None:
 
1051
                stop_revision = other.last_revision()
 
1052
                if stop_revision is None:
 
1053
                    # if there are no commits, we're done.
 
1054
                    return
 
1055
            # whats the current last revision, before we fetch [and change it
 
1056
            # possibly]
 
1057
            last_rev = self.last_revision()
 
1058
            # we fetch here regardless of whether we need to so that we pickup
 
1059
            # filled in ghosts.
 
1060
            self.fetch(other, stop_revision)
 
1061
            my_ancestry = self.repository.get_ancestry(last_rev)
 
1062
            if stop_revision in my_ancestry:
 
1063
                # last_revision is a descendant of stop_revision
 
1064
                return
 
1065
            # stop_revision must be a descendant of last_revision
 
1066
            stop_graph = self.repository.get_revision_graph(stop_revision)
 
1067
            if last_rev is not None and last_rev not in stop_graph:
 
1068
                # our previous tip is not merged into stop_revision
 
1069
                raise errors.DivergedBranches(self, other)
 
1070
            # make a new revision history from the graph
 
1071
            current_rev_id = stop_revision
 
1072
            new_history = []
 
1073
            while current_rev_id not in (None, NULL_REVISION):
 
1074
                new_history.append(current_rev_id)
 
1075
                current_rev_id_parents = stop_graph[current_rev_id]
 
1076
                try:
 
1077
                    current_rev_id = current_rev_id_parents[0]
 
1078
                except IndexError:
 
1079
                    current_rev_id = None
 
1080
            new_history.reverse()
 
1081
            self.set_revision_history(new_history)
 
1082
        finally:
 
1083
            other.unlock()
 
1084
 
 
1085
    @deprecated_method(zero_eight)
 
1086
    def pullable_revisions(self, other, stop_revision):
 
1087
        """Please use bzrlib.missing instead."""
 
1088
        other_revno = other.revision_id_to_revno(stop_revision)
 
1089
        try:
 
1090
            return self.missing_revisions(other, other_revno)
 
1091
        except DivergedBranches, e:
 
1092
            try:
 
1093
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
1094
                                                          stop_revision, 
 
1095
                                                          self.repository)
 
1096
                assert self.last_revision() not in pullable_revs
 
1097
                return pullable_revs
 
1098
            except bzrlib.errors.NotAncestor:
 
1099
                if is_ancestor(self.last_revision(), stop_revision, self):
 
1100
                    return []
 
1101
                else:
 
1102
                    raise e
 
1103
        
 
1104
    def basis_tree(self):
 
1105
        """See Branch.basis_tree."""
 
1106
        return self.repository.revision_tree(self.last_revision())
 
1107
 
 
1108
    @deprecated_method(zero_eight)
 
1109
    def working_tree(self):
 
1110
        """Create a Working tree object for this branch."""
 
1111
        from bzrlib.workingtree import WorkingTree
 
1112
        from bzrlib.transport.local import LocalTransport
 
1113
        if (self.base.find('://') != -1 or 
 
1114
            not isinstance(self._transport, LocalTransport)):
 
1115
            raise NoWorkingTree(self.base)
 
1116
        return self.bzrdir.open_workingtree()
 
1117
 
 
1118
    @needs_write_lock
 
1119
    def pull(self, source, overwrite=False, stop_revision=None):
 
1120
        """See Branch.pull."""
 
1121
        source.lock_read()
 
1122
        try:
 
1123
            old_count = len(self.revision_history())
 
1124
            try:
 
1125
                self.update_revisions(source,stop_revision)
 
1126
            except DivergedBranches:
 
1127
                if not overwrite:
 
1128
                    raise
 
1129
            if overwrite:
 
1130
                self.set_revision_history(source.revision_history())
 
1131
            new_count = len(self.revision_history())
 
1132
            return new_count - old_count
 
1133
        finally:
 
1134
            source.unlock()
 
1135
 
 
1136
    def get_parent(self):
 
1137
        """See Branch.get_parent."""
 
1138
        import errno
 
1139
        _locs = ['parent', 'pull', 'x-pull']
 
1140
        for l in _locs:
 
1141
            try:
 
1142
                return self.control_files.get_utf8(l).read().strip('\n')
 
1143
            except NoSuchFile:
 
1144
                pass
 
1145
        return None
 
1146
 
 
1147
    def get_push_location(self):
 
1148
        """See Branch.get_push_location."""
 
1149
        config = bzrlib.config.BranchConfig(self)
 
1150
        push_loc = config.get_user_option('push_location')
 
1151
        return push_loc
 
1152
 
 
1153
    def set_push_location(self, location):
 
1154
        """See Branch.set_push_location."""
 
1155
        config = bzrlib.config.LocationConfig(self.base)
 
1156
        config.set_user_option('push_location', location)
 
1157
 
 
1158
    @needs_write_lock
 
1159
    def set_parent(self, url):
 
1160
        """See Branch.set_parent."""
 
1161
        # TODO: Maybe delete old location files?
 
1162
        # URLs should never be unicode, even on the local fs,
 
1163
        # FIXUP this and get_parent in a future branch format bump:
 
1164
        # read and rewrite the file, and have the new format code read
 
1165
        # using .get not .get_utf8. RBC 20060125
 
1166
        if url is None:
 
1167
            self.control_files._transport.delete('parent')
 
1168
        else:
 
1169
            self.control_files.put_utf8('parent', url + '\n')
 
1170
 
 
1171
    def tree_config(self):
 
1172
        return TreeConfig(self)
 
1173
 
 
1174
 
 
1175
class BzrBranch5(BzrBranch):
 
1176
    """A format 5 branch. This supports new features over plan branches.
 
1177
 
 
1178
    It has support for a master_branch which is the data for bound branches.
 
1179
    """
 
1180
 
 
1181
    def __init__(self,
 
1182
                 _format,
 
1183
                 _control_files,
 
1184
                 a_bzrdir,
 
1185
                 _repository):
 
1186
        super(BzrBranch5, self).__init__(_format=_format,
 
1187
                                         _control_files=_control_files,
 
1188
                                         a_bzrdir=a_bzrdir,
 
1189
                                         _repository=_repository)
 
1190
        
 
1191
    @needs_write_lock
 
1192
    def pull(self, source, overwrite=False, stop_revision=None):
 
1193
        """Updates branch.pull to be bound branch aware."""
 
1194
        bound_location = self.get_bound_location()
 
1195
        if source.base != bound_location:
 
1196
            # not pulling from master, so we need to update master.
 
1197
            master_branch = self.get_master_branch()
 
1198
            if master_branch:
 
1199
                master_branch.pull(source)
 
1200
                source = master_branch
 
1201
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
1202
 
 
1203
    def get_bound_location(self):
 
1204
        try:
 
1205
            return self.control_files.get_utf8('bound').read()[:-1]
 
1206
        except errors.NoSuchFile:
 
1207
            return None
 
1208
 
 
1209
    @needs_read_lock
 
1210
    def get_master_branch(self):
 
1211
        """Return the branch we are bound to.
 
1212
        
 
1213
        :return: Either a Branch, or None
 
1214
 
 
1215
        This could memoise the branch, but if thats done
 
1216
        it must be revalidated on each new lock.
 
1217
        So for now we just dont memoise it.
 
1218
        # RBC 20060304 review this decision.
 
1219
        """
 
1220
        bound_loc = self.get_bound_location()
 
1221
        if not bound_loc:
 
1222
            return None
 
1223
        try:
 
1224
            return Branch.open(bound_loc)
 
1225
        except (errors.NotBranchError, errors.ConnectionError), e:
 
1226
            raise errors.BoundBranchConnectionFailure(
 
1227
                    self, bound_loc, e)
 
1228
 
 
1229
    @needs_write_lock
 
1230
    def set_bound_location(self, location):
 
1231
        """Set the target where this branch is bound to.
 
1232
 
 
1233
        :param location: URL to the target branch
 
1234
        """
 
1235
        if location:
 
1236
            self.control_files.put_utf8('bound', location+'\n')
 
1237
        else:
 
1238
            try:
 
1239
                self.control_files._transport.delete('bound')
 
1240
            except NoSuchFile:
 
1241
                return False
 
1242
            return True
 
1243
 
 
1244
    @needs_write_lock
 
1245
    def bind(self, other):
 
1246
        """Bind the local branch the other branch.
 
1247
 
 
1248
        :param other: The branch to bind to
 
1249
        :type other: Branch
 
1250
        """
 
1251
        # TODO: jam 20051230 Consider checking if the target is bound
 
1252
        #       It is debatable whether you should be able to bind to
 
1253
        #       a branch which is itself bound.
 
1254
        #       Committing is obviously forbidden,
 
1255
        #       but binding itself may not be.
 
1256
        #       Since we *have* to check at commit time, we don't
 
1257
        #       *need* to check here
 
1258
        self.pull(other)
 
1259
 
 
1260
        # we are now equal to or a suffix of other.
 
1261
 
 
1262
        # Since we have 'pulled' from the remote location,
 
1263
        # now we should try to pull in the opposite direction
 
1264
        # in case the local tree has more revisions than the
 
1265
        # remote one.
 
1266
        # There may be a different check you could do here
 
1267
        # rather than actually trying to install revisions remotely.
 
1268
        # TODO: capture an exception which indicates the remote branch
 
1269
        #       is not writeable. 
 
1270
        #       If it is up-to-date, this probably should not be a failure
 
1271
        
 
1272
        # lock other for write so the revision-history syncing cannot race
 
1273
        other.lock_write()
 
1274
        try:
 
1275
            other.pull(self)
 
1276
            # if this does not error, other now has the same last rev we do
 
1277
            # it can only error if the pull from other was concurrent with
 
1278
            # a commit to other from someone else.
 
1279
 
 
1280
            # until we ditch revision-history, we need to sync them up:
 
1281
            self.set_revision_history(other.revision_history())
 
1282
            # now other and self are up to date with each other and have the
 
1283
            # same revision-history.
 
1284
        finally:
 
1285
            other.unlock()
 
1286
 
 
1287
        self.set_bound_location(other.base)
 
1288
 
 
1289
    @needs_write_lock
 
1290
    def unbind(self):
 
1291
        """If bound, unbind"""
 
1292
        return self.set_bound_location(None)
 
1293
 
 
1294
    @needs_write_lock
 
1295
    def update(self):
 
1296
        """Synchronise this branch with the master branch if any. 
 
1297
 
 
1298
        :return: None or the last_revision that was pivoted out during the
 
1299
                 update.
 
1300
        """
 
1301
        master = self.get_master_branch()
 
1302
        if master is not None:
 
1303
            old_tip = self.last_revision()
 
1304
            self.pull(master, overwrite=True)
 
1305
            if old_tip in self.repository.get_ancestry(self.last_revision()):
 
1306
                return None
 
1307
            return old_tip
 
1308
        return None
 
1309
 
 
1310
 
 
1311
class BranchTestProviderAdapter(object):
 
1312
    """A tool to generate a suite testing multiple branch formats at once.
 
1313
 
 
1314
    This is done by copying the test once for each transport and injecting
 
1315
    the transport_server, transport_readonly_server, and branch_format
 
1316
    classes into each copy. Each copy is also given a new id() to make it
 
1317
    easy to identify.
 
1318
    """
 
1319
 
 
1320
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1321
        self._transport_server = transport_server
 
1322
        self._transport_readonly_server = transport_readonly_server
 
1323
        self._formats = formats
 
1324
    
 
1325
    def adapt(self, test):
 
1326
        result = TestSuite()
 
1327
        for branch_format, bzrdir_format in self._formats:
 
1328
            new_test = deepcopy(test)
 
1329
            new_test.transport_server = self._transport_server
 
1330
            new_test.transport_readonly_server = self._transport_readonly_server
 
1331
            new_test.bzrdir_format = bzrdir_format
 
1332
            new_test.branch_format = branch_format
 
1333
            def make_new_test_id():
 
1334
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
1335
                return lambda: new_id
 
1336
            new_test.id = make_new_test_id()
 
1337
            result.addTest(new_test)
 
1338
        return result
 
1339
 
 
1340
 
 
1341
######################################################################
 
1342
# predicates
 
1343
 
 
1344
 
 
1345
@deprecated_function(zero_eight)
 
1346
def ScratchBranch(*args, **kwargs):
 
1347
    """See bzrlib.bzrdir.ScratchDir."""
 
1348
    d = ScratchDir(*args, **kwargs)
 
1349
    return d.open_branch()
 
1350
 
 
1351
 
 
1352
@deprecated_function(zero_eight)
 
1353
def is_control_file(*args, **kwargs):
 
1354
    """See bzrlib.workingtree.is_control_file."""
 
1355
    return bzrlib.workingtree.is_control_file(*args, **kwargs)