/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: Martin Pool
  • Date: 2006-01-13 06:38:56 UTC
  • mto: (1185.65.28 storage)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: mbp@sourcefrog.net-20060113063856-484eed116191727b
Pass through wrapped function name and docstrign 
in needs_read_lock and needs_write_lock decorators.  Test this works.
(Suggestion from John)

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