/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

[merge] from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 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
import shutil
 
19
import sys
 
20
import os
 
21
import errno
 
22
from warnings import warn
 
23
from cStringIO import StringIO
 
24
 
 
25
 
 
26
import bzrlib
 
27
from bzrlib.trace import mutter, note
 
28
from bzrlib.osutils import (isdir, quotefn,
 
29
                            rename, splitpath, sha_file,
 
30
                            file_kind, abspath, normpath, pathjoin)
 
31
import bzrlib.errors as errors
 
32
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
33
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
34
                           DivergedBranches, LockError, UnlistableStore,
 
35
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
36
                           NoWorkingTree)
 
37
from bzrlib.textui import show_status
 
38
from bzrlib.config import TreeConfig
 
39
from bzrlib.delta import compare_trees
 
40
import bzrlib.inventory as inventory
 
41
from bzrlib.inventory import Inventory
 
42
from bzrlib.lockable_files import LockableFiles
 
43
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
 
44
from bzrlib.repository import Repository
 
45
from bzrlib.store import copy_all
 
46
import bzrlib.transactions as transactions
 
47
from bzrlib.transport import Transport, get_transport
 
48
from bzrlib.tree import EmptyTree, RevisionTree
 
49
import bzrlib.ui
 
50
import bzrlib.xml5
 
51
 
 
52
 
 
53
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
54
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
55
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
56
## TODO: Maybe include checks for common corruption of newlines, etc?
 
57
 
 
58
 
 
59
# TODO: Some operations like log might retrieve the same revisions
 
60
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
61
# cache in memory to make this faster.  In general anything can be
 
62
# cached in memory between lock and unlock operations.
 
63
 
 
64
def find_branch(*ignored, **ignored_too):
 
65
    # XXX: leave this here for about one release, then remove it
 
66
    raise NotImplementedError('find_branch() is not supported anymore, '
 
67
                              'please use one of the new branch constructors')
 
68
 
 
69
 
 
70
def needs_read_lock(unbound):
 
71
    """Decorate unbound to take out and release a read lock."""
 
72
    def decorated(self, *args, **kwargs):
 
73
        self.lock_read()
 
74
        try:
 
75
            return unbound(self, *args, **kwargs)
 
76
        finally:
 
77
            self.unlock()
 
78
    return decorated
 
79
 
 
80
 
 
81
def needs_write_lock(unbound):
 
82
    """Decorate unbound to take out and release a write lock."""
 
83
    def decorated(self, *args, **kwargs):
 
84
        self.lock_write()
 
85
        try:
 
86
            return unbound(self, *args, **kwargs)
 
87
        finally:
 
88
            self.unlock()
 
89
    return decorated
 
90
 
 
91
######################################################################
 
92
# branch objects
 
93
 
 
94
class Branch(object):
 
95
    """Branch holding a history of revisions.
 
96
 
 
97
    base
 
98
        Base directory/url of the branch.
 
99
    """
 
100
    base = None
 
101
 
 
102
    def __init__(self, *ignored, **ignored_too):
 
103
        raise NotImplementedError('The Branch class is abstract')
 
104
 
 
105
    @staticmethod
 
106
    def open_downlevel(base):
 
107
        """Open a branch which may be of an old format.
 
108
        
 
109
        Only local branches are supported."""
 
110
        return BzrBranch(get_transport(base), relax_version_check=True)
 
111
        
 
112
    @staticmethod
 
113
    def open(base):
 
114
        """Open an existing branch, rooted at 'base' (url)"""
 
115
        t = get_transport(base)
 
116
        mutter("trying to open %r with transport %r", base, t)
 
117
        return BzrBranch(t)
 
118
 
 
119
    @staticmethod
 
120
    def open_containing(url):
 
121
        """Open an existing branch which contains url.
 
122
        
 
123
        This probes for a branch at url, and searches upwards from there.
 
124
 
 
125
        Basically we keep looking up until we find the control directory or
 
126
        run into the root.  If there isn't one, raises NotBranchError.
 
127
        If there is one, it is returned, along with the unused portion of url.
 
128
        """
 
129
        t = get_transport(url)
 
130
        while True:
 
131
            try:
 
132
                return BzrBranch(t), t.relpath(url)
 
133
            except NotBranchError, e:
 
134
                mutter('not a branch in: %r %s', t.base, e)
 
135
            new_t = t.clone('..')
 
136
            if new_t.base == t.base:
 
137
                # reached the root, whatever that may be
 
138
                raise NotBranchError(path=url)
 
139
            t = new_t
 
140
 
 
141
    @staticmethod
 
142
    def initialize(base):
 
143
        """Create a new branch, rooted at 'base' (url)"""
 
144
        t = get_transport(unicode(base))
 
145
        return BzrBranch(t, init=True)
 
146
 
 
147
    def setup_caching(self, cache_root):
 
148
        """Subclasses that care about caching should override this, and set
 
149
        up cached stores located under cache_root.
 
150
        """
 
151
        self.cache_root = cache_root
 
152
 
 
153
    def _get_nick(self):
 
154
        cfg = self.tree_config()
 
155
        return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
 
156
 
 
157
    def _set_nick(self, nick):
 
158
        cfg = self.tree_config()
 
159
        cfg.set_option(nick, "nickname")
 
160
        assert cfg.get_option("nickname") == nick
 
161
 
 
162
    nick = property(_get_nick, _set_nick)
 
163
        
 
164
    def push_stores(self, branch_to):
 
165
        """Copy the content of this branches store to branch_to."""
 
166
        raise NotImplementedError('push_stores is abstract')
 
167
 
 
168
    def lock_write(self):
 
169
        raise NotImplementedError('lock_write is abstract')
 
170
        
 
171
    def lock_read(self):
 
172
        raise NotImplementedError('lock_read is abstract')
 
173
 
 
174
    def unlock(self):
 
175
        raise NotImplementedError('unlock is abstract')
 
176
 
 
177
    def abspath(self, name):
 
178
        """Return absolute filename for something in the branch
 
179
        
 
180
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
181
        method and not a tree method.
 
182
        """
 
183
        raise NotImplementedError('abspath is abstract')
 
184
 
 
185
    def get_root_id(self):
 
186
        """Return the id of this branches root"""
 
187
        raise NotImplementedError('get_root_id is abstract')
 
188
 
 
189
    def print_file(self, file, revision_id):
 
190
        """Print `file` to stdout."""
 
191
        raise NotImplementedError('print_file is abstract')
 
192
 
 
193
    def append_revision(self, *revision_ids):
 
194
        raise NotImplementedError('append_revision is abstract')
 
195
 
 
196
    def set_revision_history(self, rev_history):
 
197
        raise NotImplementedError('set_revision_history is abstract')
 
198
 
 
199
    def revision_history(self):
 
200
        """Return sequence of revision hashes on to this branch."""
 
201
        raise NotImplementedError('revision_history is abstract')
 
202
 
 
203
    def revno(self):
 
204
        """Return current revision number for this branch.
 
205
 
 
206
        That is equivalent to the number of revisions committed to
 
207
        this branch.
 
208
        """
 
209
        return len(self.revision_history())
 
210
 
 
211
    def last_revision(self):
 
212
        """Return last patch hash, or None if no history."""
 
213
        ph = self.revision_history()
 
214
        if ph:
 
215
            return ph[-1]
 
216
        else:
 
217
            return None
 
218
 
 
219
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
220
        """Return a list of new revisions that would perfectly fit.
 
221
        
 
222
        If self and other have not diverged, return a list of the revisions
 
223
        present in other, but missing from self.
 
224
 
 
225
        >>> from bzrlib.commit import commit
 
226
        >>> bzrlib.trace.silent = True
 
227
        >>> br1 = ScratchBranch()
 
228
        >>> br2 = ScratchBranch()
 
229
        >>> br1.missing_revisions(br2)
 
230
        []
 
231
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
232
        >>> br1.missing_revisions(br2)
 
233
        [u'REVISION-ID-1']
 
234
        >>> br2.missing_revisions(br1)
 
235
        []
 
236
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
237
        >>> br1.missing_revisions(br2)
 
238
        []
 
239
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
240
        >>> br1.missing_revisions(br2)
 
241
        [u'REVISION-ID-2A']
 
242
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
243
        >>> br1.missing_revisions(br2)
 
244
        Traceback (most recent call last):
 
245
        DivergedBranches: These branches have diverged.  Try merge.
 
246
        """
 
247
        self_history = self.revision_history()
 
248
        self_len = len(self_history)
 
249
        other_history = other.revision_history()
 
250
        other_len = len(other_history)
 
251
        common_index = min(self_len, other_len) -1
 
252
        if common_index >= 0 and \
 
253
            self_history[common_index] != other_history[common_index]:
 
254
            raise DivergedBranches(self, other)
 
255
 
 
256
        if stop_revision is None:
 
257
            stop_revision = other_len
 
258
        else:
 
259
            assert isinstance(stop_revision, int)
 
260
            if stop_revision > other_len:
 
261
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
262
        return other_history[self_len:stop_revision]
 
263
 
 
264
    
 
265
    def update_revisions(self, other, stop_revision=None):
 
266
        """Pull in new perfect-fit revisions."""
 
267
        raise NotImplementedError('update_revisions is abstract')
 
268
 
 
269
    def pullable_revisions(self, other, stop_revision):
 
270
        raise NotImplementedError('pullable_revisions is abstract')
 
271
        
 
272
    def revision_id_to_revno(self, revision_id):
 
273
        """Given a revision id, return its revno"""
 
274
        if revision_id is None:
 
275
            return 0
 
276
        history = self.revision_history()
 
277
        try:
 
278
            return history.index(revision_id) + 1
 
279
        except ValueError:
 
280
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
281
 
 
282
    def get_rev_id(self, revno, history=None):
 
283
        """Find the revision id of the specified revno."""
 
284
        if revno == 0:
 
285
            return None
 
286
        if history is None:
 
287
            history = self.revision_history()
 
288
        elif revno <= 0 or revno > len(history):
 
289
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
290
        return history[revno - 1]
 
291
 
 
292
    def working_tree(self):
 
293
        """Return a `Tree` for the working copy if this is a local branch."""
 
294
        raise NotImplementedError('working_tree is abstract')
 
295
 
 
296
    def pull(self, source, overwrite=False):
 
297
        raise NotImplementedError('pull is abstract')
 
298
 
 
299
    def basis_tree(self):
 
300
        """Return `Tree` object for last revision.
 
301
 
 
302
        If there are no revisions yet, return an `EmptyTree`.
 
303
        """
 
304
        return self.repository.revision_tree(self.last_revision())
 
305
 
 
306
    def rename_one(self, from_rel, to_rel):
 
307
        """Rename one file.
 
308
 
 
309
        This can change the directory or the filename or both.
 
310
        """
 
311
        raise NotImplementedError('rename_one is abstract')
 
312
 
 
313
    def move(self, from_paths, to_name):
 
314
        """Rename files.
 
315
 
 
316
        to_name must exist as a versioned directory.
 
317
 
 
318
        If to_name exists and is a directory, the files are moved into
 
319
        it, keeping their old names.  If it is a directory, 
 
320
 
 
321
        Note that to_name is only the last component of the new name;
 
322
        this doesn't change the directory.
 
323
 
 
324
        This returns a list of (from_path, to_path) pairs for each
 
325
        entry that is moved.
 
326
        """
 
327
        raise NotImplementedError('move is abstract')
 
328
 
 
329
    def get_parent(self):
 
330
        """Return the parent location of the branch.
 
331
 
 
332
        This is the default location for push/pull/missing.  The usual
 
333
        pattern is that the user can override it by specifying a
 
334
        location.
 
335
        """
 
336
        raise NotImplementedError('get_parent is abstract')
 
337
 
 
338
    def get_push_location(self):
 
339
        """Return the None or the location to push this branch to."""
 
340
        raise NotImplementedError('get_push_location is abstract')
 
341
 
 
342
    def set_push_location(self, location):
 
343
        """Set a new push location for this branch."""
 
344
        raise NotImplementedError('set_push_location is abstract')
 
345
 
 
346
    def set_parent(self, url):
 
347
        raise NotImplementedError('set_parent is abstract')
 
348
 
 
349
    def check_revno(self, revno):
 
350
        """\
 
351
        Check whether a revno corresponds to any revision.
 
352
        Zero (the NULL revision) is considered valid.
 
353
        """
 
354
        if revno != 0:
 
355
            self.check_real_revno(revno)
 
356
            
 
357
    def check_real_revno(self, revno):
 
358
        """\
 
359
        Check whether a revno corresponds to a real revision.
 
360
        Zero (the NULL revision) is considered invalid
 
361
        """
 
362
        if revno < 1 or revno > self.revno():
 
363
            raise InvalidRevisionNumber(revno)
 
364
        
 
365
    def sign_revision(self, revision_id, gpg_strategy):
 
366
        raise NotImplementedError('sign_revision is abstract')
 
367
 
 
368
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
369
        raise NotImplementedError('store_revision_signature is abstract')
 
370
 
 
371
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
372
        """Copy this branch into the existing directory to_location.
 
373
 
 
374
        Returns the newly created branch object.
 
375
 
 
376
        revision
 
377
            If not None, only revisions up to this point will be copied.
 
378
            The head of the new branch will be that revision.  Must be a
 
379
            revid or None.
 
380
    
 
381
        to_location -- The destination directory; must either exist and be 
 
382
            empty, or not exist, in which case it is created.
 
383
    
 
384
        basis_branch
 
385
            A local branch to copy revisions from, related to this branch. 
 
386
            This is used when branching from a remote (slow) branch, and we have
 
387
            a local branch that might contain some relevant revisions.
 
388
    
 
389
        to_branch_type
 
390
            Branch type of destination branch
 
391
        """
 
392
        assert isinstance(to_location, basestring)
 
393
        if not bzrlib.osutils.lexists(to_location):
 
394
            os.mkdir(to_location)
 
395
        if to_branch_type is None:
 
396
            to_branch_type = BzrBranch
 
397
        br_to = to_branch_type.initialize(to_location)
 
398
        mutter("copy branch from %s to %s", self, br_to)
 
399
        if basis_branch is not None:
 
400
            basis_branch.push_stores(br_to)
 
401
        br_to.working_tree().set_root_id(self.get_root_id())
 
402
        if revision is None:
 
403
            revision = self.last_revision()
 
404
        br_to.update_revisions(self, stop_revision=revision)
 
405
        br_to.set_parent(self.base)
 
406
        # circular import protection
 
407
        from bzrlib.merge import build_working_dir
 
408
        build_working_dir(to_location)
 
409
        mutter("copied")
 
410
        return br_to
 
411
 
 
412
class BzrBranch(Branch):
 
413
    """A branch stored in the actual filesystem.
 
414
 
 
415
    Note that it's "local" in the context of the filesystem; it doesn't
 
416
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
417
    it's writable, and can be accessed via the normal filesystem API.
 
418
 
 
419
    """
 
420
    # We actually expect this class to be somewhat short-lived; part of its
 
421
    # purpose is to try to isolate what bits of the branch logic are tied to
 
422
    # filesystem access, so that in a later step, we can extricate them to
 
423
    # a separarte ("storage") class.
 
424
    _inventory_weave = None
 
425
    
 
426
    # Map some sort of prefix into a namespace
 
427
    # stuff like "revno:10", "revid:", etc.
 
428
    # This should match a prefix with a function which accepts
 
429
    REVISION_NAMESPACES = {}
 
430
 
 
431
    def push_stores(self, branch_to):
 
432
        """See Branch.push_stores."""
 
433
        if (self._branch_format != branch_to._branch_format
 
434
            or self._branch_format != 4):
 
435
            from bzrlib.fetch import greedy_fetch
 
436
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
437
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
438
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
439
                         revision=self.last_revision())
 
440
            return
 
441
 
 
442
        store_pairs = ((self.text_store,      branch_to.text_store),
 
443
                       (self.inventory_store, branch_to.inventory_store),
 
444
                       (self.revision_store,  branch_to.revision_store))
 
445
        try:
 
446
            for from_store, to_store in store_pairs: 
 
447
                copy_all(from_store, to_store)
 
448
        except UnlistableStore:
 
449
            raise UnlistableBranch(from_store)
 
450
 
 
451
    def __init__(self, transport, init=False,
 
452
                 relax_version_check=False):
 
453
        """Create new branch object at a particular location.
 
454
 
 
455
        transport -- A Transport object, defining how to access files.
 
456
        
 
457
        init -- If True, create new control files in a previously
 
458
             unversioned directory.  If False, the branch must already
 
459
             be versioned.
 
460
 
 
461
        relax_version_check -- If true, the usual check for the branch
 
462
            version is not applied.  This is intended only for
 
463
            upgrade/recovery type use; it's not guaranteed that
 
464
            all operations will work on old format branches.
 
465
 
 
466
        In the test suite, creation of new trees is tested using the
 
467
        `ScratchBranch` class.
 
468
        """
 
469
        assert isinstance(transport, Transport), \
 
470
            "%r is not a Transport" % transport
 
471
        # TODO: jam 20060103 We create a clone of this transport at .bzr/
 
472
        #       and then we forget about it, should we keep a handle to it?
 
473
        self._base = transport.base
 
474
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR),
 
475
                                           'branch-lock')
 
476
        if init:
 
477
            self._make_control()
 
478
        self._check_format(relax_version_check)
 
479
        self.repository = Repository(transport, self._branch_format)
 
480
 
 
481
    def __str__(self):
 
482
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
483
 
 
484
    __repr__ = __str__
 
485
 
 
486
    def __del__(self):
 
487
        # TODO: It might be best to do this somewhere else,
 
488
        # but it is nice for a Branch object to automatically
 
489
        # cache it's information.
 
490
        # Alternatively, we could have the Transport objects cache requests
 
491
        # See the earlier discussion about how major objects (like Branch)
 
492
        # should never expect their __del__ function to run.
 
493
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
494
            try:
 
495
                shutil.rmtree(self.cache_root)
 
496
            except:
 
497
                pass
 
498
            self.cache_root = None
 
499
 
 
500
    def _get_base(self):
 
501
        return self._base
 
502
 
 
503
    base = property(_get_base, doc="The URL for the root of this branch.")
 
504
 
 
505
    def _finish_transaction(self):
 
506
        """Exit the current transaction."""
 
507
        return self.control_files._finish_transaction()
 
508
 
 
509
    def get_transaction(self):
 
510
        """Return the current active transaction.
 
511
 
 
512
        If no transaction is active, this returns a passthrough object
 
513
        for which all data is immediately flushed and no caching happens.
 
514
        """
 
515
        # this is an explicit function so that we can do tricky stuff
 
516
        # when the storage in rev_storage is elsewhere.
 
517
        # we probably need to hook the two 'lock a location' and 
 
518
        # 'have a transaction' together more delicately, so that
 
519
        # we can have two locks (branch and storage) and one transaction
 
520
        # ... and finishing the transaction unlocks both, but unlocking
 
521
        # does not. - RBC 20051121
 
522
        return self.control_files.get_transaction()
 
523
 
 
524
    def _set_transaction(self, transaction):
 
525
        """Set a new active transaction."""
 
526
        return self.control_files._set_transaction(transaction)
 
527
 
 
528
    def abspath(self, name):
 
529
        """See Branch.abspath."""
 
530
        return self.control_files._transport.abspath(name)
 
531
 
 
532
    def _make_control(self):
 
533
        from bzrlib.inventory import Inventory
 
534
        from bzrlib.weavefile import write_weave_v5
 
535
        from bzrlib.weave import Weave
 
536
        
 
537
        # Create an empty inventory
 
538
        sio = StringIO()
 
539
        # if we want per-tree root ids then this is the place to set
 
540
        # them; they're not needed for now and so ommitted for
 
541
        # simplicity.
 
542
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
543
        empty_inv = sio.getvalue()
 
544
        sio = StringIO()
 
545
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
546
        empty_weave = sio.getvalue()
 
547
 
 
548
        dirs = ['', 'revision-store', 'weaves']
 
549
        files = [('README', 
 
550
            "This is a Bazaar-NG control directory.\n"
 
551
            "Do not change any files in this directory.\n"),
 
552
            ('branch-format', BZR_BRANCH_FORMAT_6),
 
553
            ('revision-history', ''),
 
554
            ('branch-name', ''),
 
555
            ('branch-lock', ''),
 
556
            ('pending-merges', ''),
 
557
            ('inventory', empty_inv),
 
558
            ('inventory.weave', empty_weave),
 
559
            ('ancestry.weave', empty_weave)
 
560
        ]
 
561
        cfe = self.control_files._escape
 
562
        self.control_files._transport.mkdir_multi([cfe(d) for d in dirs],
 
563
                mode=self.control_files._dir_mode)
 
564
        self.control_files.lock_write()
 
565
        try:
 
566
            for file, content in files:
 
567
                self.control_files.put_utf8(file, content)
 
568
            mutter('created control directory in ' + self.base)
 
569
        finally:
 
570
            self.control_files.unlock()
 
571
 
 
572
    def _check_format(self, relax_version_check):
 
573
        """Check this branch format is supported.
 
574
 
 
575
        The format level is stored, as an integer, in
 
576
        self._branch_format for code that needs to check it later.
 
577
 
 
578
        In the future, we might need different in-memory Branch
 
579
        classes to support downlevel branches.  But not yet.
 
580
        """
 
581
        try:
 
582
            fmt = self.control_files.controlfile('branch-format', 'r').read()
 
583
        except NoSuchFile:
 
584
            raise NotBranchError(path=self.base)
 
585
        mutter("got branch format %r", fmt)
 
586
        if fmt == BZR_BRANCH_FORMAT_6:
 
587
            self._branch_format = 6
 
588
        elif fmt == BZR_BRANCH_FORMAT_5:
 
589
            self._branch_format = 5
 
590
        elif fmt == BZR_BRANCH_FORMAT_4:
 
591
            self._branch_format = 4
 
592
 
 
593
        if (not relax_version_check
 
594
            and self._branch_format not in (5, 6)):
 
595
            raise errors.UnsupportedFormatError(
 
596
                           'sorry, branch format %r not supported' % fmt,
 
597
                           ['use a different bzr version',
 
598
                            'or remove the .bzr directory'
 
599
                            ' and "bzr init" again'])
 
600
 
 
601
    @needs_read_lock
 
602
    def get_root_id(self):
 
603
        """See Branch.get_root_id."""
 
604
        inv = self.repository.get_inventory(self.last_revision())
 
605
        return inv.root.file_id
 
606
 
 
607
    def lock_write(self):
 
608
        # TODO: test for failed two phase locks. This is known broken.
 
609
        self.control_files.lock_write()
 
610
        self.repository.lock_write()
 
611
 
 
612
    def lock_read(self):
 
613
        # TODO: test for failed two phase locks. This is known broken.
 
614
        self.control_files.lock_read()
 
615
        self.repository.lock_read()
 
616
 
 
617
    def unlock(self):
 
618
        # TODO: test for failed two phase locks. This is known broken.
 
619
        self.repository.unlock()
 
620
        self.control_files.unlock()
 
621
 
 
622
    @needs_read_lock
 
623
    def print_file(self, file, revision_id):
 
624
        """See Branch.print_file."""
 
625
        return self.repository.print_file(file, revision_id)
 
626
 
 
627
    @needs_write_lock
 
628
    def append_revision(self, *revision_ids):
 
629
        """See Branch.append_revision."""
 
630
        for revision_id in revision_ids:
 
631
            mutter("add {%s} to revision-history" % revision_id)
 
632
        rev_history = self.revision_history()
 
633
        rev_history.extend(revision_ids)
 
634
        self.set_revision_history(rev_history)
 
635
 
 
636
    @needs_write_lock
 
637
    def set_revision_history(self, rev_history):
 
638
        """See Branch.set_revision_history."""
 
639
        old_revision = self.last_revision()
 
640
        new_revision = rev_history[-1]
 
641
        self.control_files.put_utf8(
 
642
            'revision-history', '\n'.join(rev_history))
 
643
        try:
 
644
            # FIXME: RBC 20051207 this smells wrong, last_revision in the 
 
645
            # working tree may be != to last_revision in the branch - so
 
646
            # why is this passing in the branches last_revision ?
 
647
            self.working_tree().set_last_revision(new_revision, old_revision)
 
648
        except NoWorkingTree:
 
649
            mutter('Unable to set_last_revision without a working tree.')
 
650
 
 
651
    def get_revision_delta(self, revno):
 
652
        """Return the delta for one revision.
 
653
 
 
654
        The delta is relative to its mainline predecessor, or the
 
655
        empty tree for revision 1.
 
656
        """
 
657
        assert isinstance(revno, int)
 
658
        rh = self.revision_history()
 
659
        if not (1 <= revno <= len(rh)):
 
660
            raise InvalidRevisionNumber(revno)
 
661
 
 
662
        # revno is 1-based; list is 0-based
 
663
 
 
664
        new_tree = self.repository.revision_tree(rh[revno-1])
 
665
        if revno == 1:
 
666
            old_tree = EmptyTree()
 
667
        else:
 
668
            old_tree = self.repository.revision_tree(rh[revno-2])
 
669
        return compare_trees(old_tree, new_tree)
 
670
 
 
671
    @needs_read_lock
 
672
    def revision_history(self):
 
673
        """See Branch.revision_history."""
 
674
        # FIXME are transactions bound to control files ? RBC 20051121
 
675
        transaction = self.get_transaction()
 
676
        history = transaction.map.find_revision_history()
 
677
        if history is not None:
 
678
            mutter("cache hit for revision-history in %s", self)
 
679
            return list(history)
 
680
        history = [l.rstrip('\r\n') for l in
 
681
                self.control_files.controlfile('revision-history', 'r').readlines()]
 
682
        transaction.map.add_revision_history(history)
 
683
        # this call is disabled because revision_history is 
 
684
        # not really an object yet, and the transaction is for objects.
 
685
        # transaction.register_clean(history, precious=True)
 
686
        return list(history)
 
687
 
 
688
    def update_revisions(self, other, stop_revision=None):
 
689
        """See Branch.update_revisions."""
 
690
        from bzrlib.fetch import greedy_fetch
 
691
        if stop_revision is None:
 
692
            stop_revision = other.last_revision()
 
693
        ### Should this be checking is_ancestor instead of revision_history?
 
694
        if (stop_revision is not None and 
 
695
            stop_revision in self.revision_history()):
 
696
            return
 
697
        greedy_fetch(to_branch=self, from_branch=other,
 
698
                     revision=stop_revision)
 
699
        pullable_revs = self.pullable_revisions(other, stop_revision)
 
700
        if len(pullable_revs) > 0:
 
701
            self.append_revision(*pullable_revs)
 
702
 
 
703
    def pullable_revisions(self, other, stop_revision):
 
704
        """See Branch.pullable_revisions."""
 
705
        other_revno = other.revision_id_to_revno(stop_revision)
 
706
        try:
 
707
            return self.missing_revisions(other, other_revno)
 
708
        except DivergedBranches, e:
 
709
            try:
 
710
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
711
                                                          stop_revision, 
 
712
                                                          self.repository)
 
713
                assert self.last_revision() not in pullable_revs
 
714
                return pullable_revs
 
715
            except bzrlib.errors.NotAncestor:
 
716
                if is_ancestor(self.last_revision(), stop_revision, self):
 
717
                    return []
 
718
                else:
 
719
                    raise e
 
720
        
 
721
    def basis_tree(self):
 
722
        """See Branch.basis_tree."""
 
723
        try:
 
724
            revision_id = self.revision_history()[-1]
 
725
            # FIXME: This is an abstraction violation, the basis tree 
 
726
            # here as defined is on the working tree, the method should
 
727
            # be too. The basis tree for a branch can be different than
 
728
            # that for a working tree. RBC 20051207
 
729
            xml = self.working_tree().read_basis_inventory(revision_id)
 
730
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
731
            return RevisionTree(self.repository, inv, revision_id)
 
732
        except (IndexError, NoSuchFile, NoWorkingTree), e:
 
733
            return self.repository.revision_tree(self.last_revision())
 
734
 
 
735
    def working_tree(self):
 
736
        """See Branch.working_tree."""
 
737
        from bzrlib.workingtree import WorkingTree
 
738
        if self.base.find('://') != -1:
 
739
            raise NoWorkingTree(self.base)
 
740
        return WorkingTree(self.base, branch=self)
 
741
 
 
742
    @needs_write_lock
 
743
    def pull(self, source, overwrite=False):
 
744
        """See Branch.pull."""
 
745
        source.lock_read()
 
746
        try:
 
747
            old_count = len(self.revision_history())
 
748
            try:
 
749
                self.update_revisions(source)
 
750
            except DivergedBranches:
 
751
                if not overwrite:
 
752
                    raise
 
753
            if overwrite:
 
754
                self.set_revision_history(source.revision_history())
 
755
            new_count = len(self.revision_history())
 
756
            return new_count - old_count
 
757
        finally:
 
758
            source.unlock()
 
759
 
 
760
    def get_parent(self):
 
761
        """See Branch.get_parent."""
 
762
        import errno
 
763
        _locs = ['parent', 'pull', 'x-pull']
 
764
        for l in _locs:
 
765
            try:
 
766
                return self.control_files.controlfile(l, 'r').read().strip('\n')
 
767
            except NoSuchFile:
 
768
                pass
 
769
        return None
 
770
 
 
771
    def get_push_location(self):
 
772
        """See Branch.get_push_location."""
 
773
        config = bzrlib.config.BranchConfig(self)
 
774
        push_loc = config.get_user_option('push_location')
 
775
        return push_loc
 
776
 
 
777
    def set_push_location(self, location):
 
778
        """See Branch.set_push_location."""
 
779
        config = bzrlib.config.LocationConfig(self.base)
 
780
        config.set_user_option('push_location', location)
 
781
 
 
782
    @needs_write_lock
 
783
    def set_parent(self, url):
 
784
        """See Branch.set_parent."""
 
785
        # TODO: Maybe delete old location files?
 
786
        from bzrlib.atomicfile import AtomicFile
 
787
        f = AtomicFile(self.control_files.controlfilename('parent'))
 
788
        try:
 
789
            f.write(url + '\n')
 
790
            f.commit()
 
791
        finally:
 
792
            f.close()
 
793
 
 
794
    def tree_config(self):
 
795
        return TreeConfig(self)
 
796
 
 
797
    def _get_truncated_history(self, revision_id):
 
798
        history = self.revision_history()
 
799
        if revision_id is None:
 
800
            return history
 
801
        try:
 
802
            idx = history.index(revision_id)
 
803
        except ValueError:
 
804
            raise InvalidRevisionId(revision_id=revision, branch=self)
 
805
        return history[:idx+1]
 
806
 
 
807
    @needs_read_lock
 
808
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
 
809
        assert isinstance(to_location, basestring)
 
810
        if basis_branch is not None:
 
811
            note("basis_branch is not supported for fast weave copy yet.")
 
812
 
 
813
        history = self._get_truncated_history(revision)
 
814
        if not bzrlib.osutils.lexists(to_location):
 
815
            os.mkdir(to_location)
 
816
        branch_to = Branch.initialize(to_location)
 
817
        mutter("copy branch from %s to %s", self, branch_to)
 
818
        branch_to.working_tree().set_root_id(self.get_root_id())
 
819
 
 
820
        self.repository.copy(branch_to.repository)
 
821
        
 
822
        # must be done *after* history is copied across
 
823
        # FIXME duplicate code with base .clone().
 
824
        # .. would template method be useful here.  RBC 20051207
 
825
        branch_to.set_parent(self.base)
 
826
        branch_to.append_revision(*history)
 
827
        # circular import protection
 
828
        from bzrlib.merge import build_working_dir
 
829
        build_working_dir(to_location)
 
830
        mutter("copied")
 
831
        return branch_to
 
832
 
 
833
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
834
        if to_branch_type is None:
 
835
            to_branch_type = BzrBranch
 
836
 
 
837
        if to_branch_type == BzrBranch \
 
838
            and self.repository.weave_store.listable() \
 
839
            and self.repository.revision_store.listable():
 
840
            return self._clone_weave(to_location, revision, basis_branch)
 
841
 
 
842
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
 
843
 
 
844
 
 
845
class ScratchBranch(BzrBranch):
 
846
    """Special test class: a branch that cleans up after itself.
 
847
 
 
848
    >>> b = ScratchBranch()
 
849
    >>> isdir(b.base)
 
850
    True
 
851
    >>> bd = b.base
 
852
    >>> b._transport.__del__()
 
853
    >>> isdir(bd)
 
854
    False
 
855
    """
 
856
 
 
857
    def __init__(self, files=[], dirs=[], transport=None):
 
858
        """Make a test branch.
 
859
 
 
860
        This creates a temporary directory and runs init-tree in it.
 
861
 
 
862
        If any files are listed, they are created in the working copy.
 
863
        """
 
864
        if transport is None:
 
865
            transport = bzrlib.transport.local.ScratchTransport()
 
866
            super(ScratchBranch, self).__init__(transport, init=True)
 
867
        else:
 
868
            super(ScratchBranch, self).__init__(transport)
 
869
 
 
870
        # BzrBranch creates a clone to .bzr and then forgets about the
 
871
        # original transport. A ScratchTransport() deletes itself and
 
872
        # everything underneath it when it goes away, so we need to
 
873
        # grab a local copy to prevent that from happening
 
874
        self._transport = transport
 
875
 
 
876
        for d in dirs:
 
877
            self._transport.mkdir(d)
 
878
            
 
879
        for f in files:
 
880
            self._transport.put(f, 'content of %s' % f)
 
881
 
 
882
    def clone(self):
 
883
        """
 
884
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
885
        >>> os.listdir(orig.base)
 
886
        [u'.bzr', u'file1', u'file2']
 
887
        >>> clone = orig.clone()
 
888
        >>> if os.name != 'nt':
 
889
        ...   os.path.samefile(orig.base, clone.base)
 
890
        ... else:
 
891
        ...   orig.base == clone.base
 
892
        ...
 
893
        False
 
894
        >>> os.listdir(clone.base)
 
895
        [u'.bzr', u'file1', u'file2']
 
896
        """
 
897
        from shutil import copytree
 
898
        from bzrlib.osutils import mkdtemp
 
899
        base = mkdtemp()
 
900
        os.rmdir(base)
 
901
        copytree(self.base, base, symlinks=True)
 
902
        return ScratchBranch(
 
903
            transport=bzrlib.transport.local.ScratchTransport(base))
 
904
    
 
905
 
 
906
######################################################################
 
907
# predicates
 
908
 
 
909
 
 
910
def is_control_file(filename):
 
911
    ## FIXME: better check
 
912
    filename = normpath(filename)
 
913
    while filename != '':
 
914
        head, tail = os.path.split(filename)
 
915
        ## mutter('check %r for control file' % ((head, tail), ))
 
916
        if tail == bzrlib.BZRDIR:
 
917
            return True
 
918
        if filename == head:
 
919
            break
 
920
        filename = head
 
921
    return False