/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: Aaron Bentley
  • Date: 2005-12-25 23:06:24 UTC
  • mto: (1185.67.11 bzr.revision-storage)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: aaron.bentley@utoronto.ca-20051225230624-f61a1538912a578a
Added tests and fixes for LockableFiles.put_utf8(); imported IterableFile

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, appendpath, 
 
30
                            file_kind, abspath)
 
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:
 
134
                pass
 
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
        self.control_files = LockableFiles(transport, 'branch-lock')
 
472
        if init:
 
473
            self._make_control()
 
474
        self._check_format(relax_version_check)
 
475
        self.repository = Repository(transport, self._branch_format)
 
476
 
 
477
    def __str__(self):
 
478
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
479
 
 
480
    __repr__ = __str__
 
481
 
 
482
    def __del__(self):
 
483
        # TODO: It might be best to do this somewhere else,
 
484
        # but it is nice for a Branch object to automatically
 
485
        # cache it's information.
 
486
        # Alternatively, we could have the Transport objects cache requests
 
487
        # See the earlier discussion about how major objects (like Branch)
 
488
        # should never expect their __del__ function to run.
 
489
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
490
            try:
 
491
                shutil.rmtree(self.cache_root)
 
492
            except:
 
493
                pass
 
494
            self.cache_root = None
 
495
 
 
496
    def _get_base(self):
 
497
        if self.control_files._transport:
 
498
            return self.control_files._transport.base
 
499
        return None
 
500
 
 
501
    base = property(_get_base, doc="The URL for the root of this branch.")
 
502
 
 
503
    def _finish_transaction(self):
 
504
        """Exit the current transaction."""
 
505
        return self.control_files._finish_transaction()
 
506
 
 
507
    def get_transaction(self):
 
508
        """Return the current active transaction.
 
509
 
 
510
        If no transaction is active, this returns a passthrough object
 
511
        for which all data is immediately flushed and no caching happens.
 
512
        """
 
513
        # this is an explicit function so that we can do tricky stuff
 
514
        # when the storage in rev_storage is elsewhere.
 
515
        # we probably need to hook the two 'lock a location' and 
 
516
        # 'have a transaction' together more delicately, so that
 
517
        # we can have two locks (branch and storage) and one transaction
 
518
        # ... and finishing the transaction unlocks both, but unlocking
 
519
        # does not. - RBC 20051121
 
520
        return self.control_files.get_transaction()
 
521
 
 
522
    def _set_transaction(self, transaction):
 
523
        """Set a new active transaction."""
 
524
        return self.control_files._set_transaction(transaction)
 
525
 
 
526
    def abspath(self, name):
 
527
        """See Branch.abspath."""
 
528
        return self.control_files._transport.abspath(name)
 
529
 
 
530
    def _make_control(self):
 
531
        from bzrlib.inventory import Inventory
 
532
        from bzrlib.weavefile import write_weave_v5
 
533
        from bzrlib.weave import Weave
 
534
        
 
535
        # Create an empty inventory
 
536
        sio = StringIO()
 
537
        # if we want per-tree root ids then this is the place to set
 
538
        # them; they're not needed for now and so ommitted for
 
539
        # simplicity.
 
540
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
541
        empty_inv = sio.getvalue()
 
542
        sio = StringIO()
 
543
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
544
        empty_weave = sio.getvalue()
 
545
 
 
546
        dirs = [[], 'revision-store', 'weaves']
 
547
        files = [('README', 
 
548
            "This is a Bazaar-NG control directory.\n"
 
549
            "Do not change any files in this directory.\n"),
 
550
            ('branch-format', BZR_BRANCH_FORMAT_6),
 
551
            ('revision-history', ''),
 
552
            ('branch-name', ''),
 
553
            ('branch-lock', ''),
 
554
            ('pending-merges', ''),
 
555
            ('inventory', empty_inv),
 
556
            ('inventory.weave', empty_weave),
 
557
            ('ancestry.weave', empty_weave)
 
558
        ]
 
559
        cfn = self.control_files._rel_controlfilename
 
560
        self.control_files._transport.mkdir_multi([cfn(d) for d in dirs])
 
561
        self.control_files.lock_write()
 
562
        try:
 
563
            for file, content in files:
 
564
                self.control_files.put_utf8(file, content)
 
565
            mutter('created control directory in ' + self.base)
 
566
        finally:
 
567
            self.control_files.unlock()
 
568
 
 
569
    def _check_format(self, relax_version_check):
 
570
        """Check this branch format is supported.
 
571
 
 
572
        The format level is stored, as an integer, in
 
573
        self._branch_format for code that needs to check it later.
 
574
 
 
575
        In the future, we might need different in-memory Branch
 
576
        classes to support downlevel branches.  But not yet.
 
577
        """
 
578
        try:
 
579
            fmt = self.control_files.controlfile('branch-format', 'r').read()
 
580
        except NoSuchFile:
 
581
            raise NotBranchError(path=self.base)
 
582
        mutter("got branch format %r", fmt)
 
583
        if fmt == BZR_BRANCH_FORMAT_6:
 
584
            self._branch_format = 6
 
585
        elif fmt == BZR_BRANCH_FORMAT_5:
 
586
            self._branch_format = 5
 
587
        elif fmt == BZR_BRANCH_FORMAT_4:
 
588
            self._branch_format = 4
 
589
 
 
590
        if (not relax_version_check
 
591
            and self._branch_format not in (5, 6)):
 
592
            raise errors.UnsupportedFormatError(
 
593
                           'sorry, branch format %r not supported' % fmt,
 
594
                           ['use a different bzr version',
 
595
                            'or remove the .bzr directory'
 
596
                            ' and "bzr init" again'])
 
597
 
 
598
    @needs_read_lock
 
599
    def get_root_id(self):
 
600
        """See Branch.get_root_id."""
 
601
        inv = self.repository.get_inventory(self.last_revision())
 
602
        return inv.root.file_id
 
603
 
 
604
    def lock_write(self):
 
605
        # TODO: test for failed two phase locks. This is known broken.
 
606
        self.control_files.lock_write()
 
607
        self.repository.lock_write()
 
608
 
 
609
    def lock_read(self):
 
610
        # TODO: test for failed two phase locks. This is known broken.
 
611
        self.control_files.lock_read()
 
612
        self.repository.lock_read()
 
613
 
 
614
    def unlock(self):
 
615
        # TODO: test for failed two phase locks. This is known broken.
 
616
        self.repository.unlock()
 
617
        self.control_files.unlock()
 
618
 
 
619
    @needs_read_lock
 
620
    def print_file(self, file, revision_id):
 
621
        """See Branch.print_file."""
 
622
        return self.repository.print_file(file, revision_id)
 
623
 
 
624
    @needs_write_lock
 
625
    def append_revision(self, *revision_ids):
 
626
        """See Branch.append_revision."""
 
627
        for revision_id in revision_ids:
 
628
            mutter("add {%s} to revision-history" % revision_id)
 
629
        rev_history = self.revision_history()
 
630
        rev_history.extend(revision_ids)
 
631
        self.set_revision_history(rev_history)
 
632
 
 
633
    @needs_write_lock
 
634
    def set_revision_history(self, rev_history):
 
635
        """See Branch.set_revision_history."""
 
636
        old_revision = self.last_revision()
 
637
        new_revision = rev_history[-1]
 
638
        self.control_files.put_utf8(
 
639
            'revision-history', '\n'.join(rev_history))
 
640
        try:
 
641
            # FIXME: RBC 20051207 this smells wrong, last_revision in the 
 
642
            # working tree may be != to last_revision in the branch - so
 
643
            # why is this passing in the branches last_revision ?
 
644
            self.working_tree().set_last_revision(new_revision, old_revision)
 
645
        except NoWorkingTree:
 
646
            mutter('Unable to set_last_revision without a working tree.')
 
647
 
 
648
 
 
649
    def get_revision_delta(self, revno):
 
650
        """Return the delta for one revision.
 
651
 
 
652
        The delta is relative to its mainline predecessor, or the
 
653
        empty tree for revision 1.
 
654
        """
 
655
        assert isinstance(revno, int)
 
656
        rh = self.revision_history()
 
657
        if not (1 <= revno <= len(rh)):
 
658
            raise InvalidRevisionNumber(revno)
 
659
 
 
660
        # revno is 1-based; list is 0-based
 
661
 
 
662
        new_tree = self.repository.revision_tree(rh[revno-1])
 
663
        if revno == 1:
 
664
            old_tree = EmptyTree()
 
665
        else:
 
666
            old_tree = self.repository.revision_tree(rh[revno-2])
 
667
        return compare_trees(old_tree, new_tree)
 
668
 
 
669
    @needs_read_lock
 
670
    def revision_history(self):
 
671
        """See Branch.revision_history."""
 
672
        # FIXME are transactions bound to control files ? RBC 20051121
 
673
        transaction = self.get_transaction()
 
674
        history = transaction.map.find_revision_history()
 
675
        if history is not None:
 
676
            mutter("cache hit for revision-history in %s", self)
 
677
            return list(history)
 
678
        history = [l.rstrip('\r\n') for l in
 
679
                self.control_files.controlfile('revision-history', 'r').readlines()]
 
680
        transaction.map.add_revision_history(history)
 
681
        # this call is disabled because revision_history is 
 
682
        # not really an object yet, and the transaction is for objects.
 
683
        # transaction.register_clean(history, precious=True)
 
684
        return list(history)
 
685
 
 
686
    def update_revisions(self, other, stop_revision=None):
 
687
        """See Branch.update_revisions."""
 
688
        from bzrlib.fetch import greedy_fetch
 
689
        if stop_revision is None:
 
690
            stop_revision = other.last_revision()
 
691
        ### Should this be checking is_ancestor instead of revision_history?
 
692
        if (stop_revision is not None and 
 
693
            stop_revision in self.revision_history()):
 
694
            return
 
695
        greedy_fetch(to_branch=self, from_branch=other,
 
696
                     revision=stop_revision)
 
697
        pullable_revs = self.pullable_revisions(other, stop_revision)
 
698
        if len(pullable_revs) > 0:
 
699
            self.append_revision(*pullable_revs)
 
700
 
 
701
    def pullable_revisions(self, other, stop_revision):
 
702
        """See Branch.pullable_revisions."""
 
703
        other_revno = other.revision_id_to_revno(stop_revision)
 
704
        try:
 
705
            return self.missing_revisions(other, other_revno)
 
706
        except DivergedBranches, e:
 
707
            try:
 
708
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
709
                                                          stop_revision, 
 
710
                                                          self.repository)
 
711
                assert self.last_revision() not in pullable_revs
 
712
                return pullable_revs
 
713
            except bzrlib.errors.NotAncestor:
 
714
                if is_ancestor(self.last_revision(), stop_revision, self):
 
715
                    return []
 
716
                else:
 
717
                    raise e
 
718
        
 
719
    def basis_tree(self):
 
720
        """See Branch.basis_tree."""
 
721
        try:
 
722
            revision_id = self.revision_history()[-1]
 
723
            # FIXME: This is an abstraction violation, the basis tree 
 
724
            # here as defined is on the working tree, the method should
 
725
            # be too. The basis tree for a branch can be different than
 
726
            # that for a working tree. RBC 20051207
 
727
            xml = self.working_tree().read_basis_inventory(revision_id)
 
728
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
729
            return RevisionTree(self.repository.weave_store, inv, revision_id)
 
730
        except (IndexError, NoSuchFile, NoWorkingTree), e:
 
731
            return self.repository.revision_tree(self.last_revision())
 
732
 
 
733
    def working_tree(self):
 
734
        """See Branch.working_tree."""
 
735
        from bzrlib.workingtree import WorkingTree
 
736
        if self.base.find('://') != -1:
 
737
            raise NoWorkingTree(self.base)
 
738
        return WorkingTree(self.base, branch=self)
 
739
 
 
740
    @needs_write_lock
 
741
    def pull(self, source, overwrite=False):
 
742
        """See Branch.pull."""
 
743
        source.lock_read()
 
744
        try:
 
745
            old_count = len(self.revision_history())
 
746
            try:
 
747
                self.update_revisions(source)
 
748
            except DivergedBranches:
 
749
                if not overwrite:
 
750
                    raise
 
751
            if overwrite:
 
752
                self.set_revision_history(source.revision_history())
 
753
            new_count = len(self.revision_history())
 
754
            return new_count - old_count
 
755
        finally:
 
756
            source.unlock()
 
757
 
 
758
    def get_parent(self):
 
759
        """See Branch.get_parent."""
 
760
        import errno
 
761
        _locs = ['parent', 'pull', 'x-pull']
 
762
        for l in _locs:
 
763
            try:
 
764
                return self.control_files.controlfile(l, 'r').read().strip('\n')
 
765
            except NoSuchFile:
 
766
                pass
 
767
        return None
 
768
 
 
769
    def get_push_location(self):
 
770
        """See Branch.get_push_location."""
 
771
        config = bzrlib.config.BranchConfig(self)
 
772
        push_loc = config.get_user_option('push_location')
 
773
        return push_loc
 
774
 
 
775
    def set_push_location(self, location):
 
776
        """See Branch.set_push_location."""
 
777
        config = bzrlib.config.LocationConfig(self.base)
 
778
        config.set_user_option('push_location', location)
 
779
 
 
780
    @needs_write_lock
 
781
    def set_parent(self, url):
 
782
        """See Branch.set_parent."""
 
783
        # TODO: Maybe delete old location files?
 
784
        from bzrlib.atomicfile import AtomicFile
 
785
        f = AtomicFile(self.control_files.controlfilename('parent'))
 
786
        try:
 
787
            f.write(url + '\n')
 
788
            f.commit()
 
789
        finally:
 
790
            f.close()
 
791
 
 
792
    def tree_config(self):
 
793
        return TreeConfig(self)
 
794
 
 
795
    def _get_truncated_history(self, revision_id):
 
796
        history = self.revision_history()
 
797
        if revision_id is None:
 
798
            return history
 
799
        try:
 
800
            idx = history.index(revision_id)
 
801
        except ValueError:
 
802
            raise InvalidRevisionId(revision_id=revision, branch=self)
 
803
        return history[:idx+1]
 
804
 
 
805
    @needs_read_lock
 
806
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
 
807
        assert isinstance(to_location, basestring)
 
808
        if basis_branch is not None:
 
809
            note("basis_branch is not supported for fast weave copy yet.")
 
810
 
 
811
        history = self._get_truncated_history(revision)
 
812
        if not bzrlib.osutils.lexists(to_location):
 
813
            os.mkdir(to_location)
 
814
        branch_to = Branch.initialize(to_location)
 
815
        mutter("copy branch from %s to %s", self, branch_to)
 
816
        branch_to.working_tree().set_root_id(self.get_root_id())
 
817
 
 
818
        self.repository.copy(branch_to.repository)
 
819
        
 
820
        # must be done *after* history is copied across
 
821
        # FIXME duplicate code with base .clone().
 
822
        # .. would template method be useful here.  RBC 20051207
 
823
        branch_to.set_parent(self.base)
 
824
        branch_to.append_revision(*history)
 
825
        # circular import protection
 
826
        from bzrlib.merge import build_working_dir
 
827
        build_working_dir(to_location)
 
828
        mutter("copied")
 
829
        return branch_to
 
830
 
 
831
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
832
        if to_branch_type is None:
 
833
            to_branch_type = BzrBranch
 
834
 
 
835
        if to_branch_type == BzrBranch \
 
836
            and self.repository.weave_store.listable() \
 
837
            and self.repository.revision_store.listable():
 
838
            return self._clone_weave(to_location, revision, basis_branch)
 
839
 
 
840
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
 
841
 
 
842
class ScratchBranch(BzrBranch):
 
843
    """Special test class: a branch that cleans up after itself.
 
844
 
 
845
    >>> b = ScratchBranch()
 
846
    >>> isdir(b.base)
 
847
    True
 
848
    >>> bd = b.base
 
849
    >>> b.control_files._transport.__del__()
 
850
    >>> isdir(bd)
 
851
    False
 
852
    """
 
853
 
 
854
    def __init__(self, files=[], dirs=[], transport=None):
 
855
        """Make a test branch.
 
856
 
 
857
        This creates a temporary directory and runs init-tree in it.
 
858
 
 
859
        If any files are listed, they are created in the working copy.
 
860
        """
 
861
        if transport is None:
 
862
            transport = bzrlib.transport.local.ScratchTransport()
 
863
            super(ScratchBranch, self).__init__(transport, init=True)
 
864
        else:
 
865
            super(ScratchBranch, self).__init__(transport)
 
866
 
 
867
        for d in dirs:
 
868
            self.control_files._transport.mkdir(d)
 
869
            
 
870
        for f in files:
 
871
            self.control_files._transport.put(f, 'content of %s' % f)
 
872
 
 
873
    def clone(self):
 
874
        """
 
875
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
876
        >>> clone = orig.clone()
 
877
        >>> if os.name != 'nt':
 
878
        ...   os.path.samefile(orig.base, clone.base)
 
879
        ... else:
 
880
        ...   orig.base == clone.base
 
881
        ...
 
882
        False
 
883
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
 
884
        True
 
885
        """
 
886
        from shutil import copytree
 
887
        from tempfile import mkdtemp
 
888
        base = mkdtemp()
 
889
        os.rmdir(base)
 
890
        copytree(self.base, base, symlinks=True)
 
891
        return ScratchBranch(
 
892
            transport=bzrlib.transport.local.ScratchTransport(base))
 
893
    
 
894
 
 
895
######################################################################
 
896
# predicates
 
897
 
 
898
 
 
899
def is_control_file(filename):
 
900
    ## FIXME: better check
 
901
    filename = os.path.normpath(filename)
 
902
    while filename != '':
 
903
        head, tail = os.path.split(filename)
 
904
        ## mutter('check %r for control file' % ((head, tail), ))
 
905
        if tail == bzrlib.BZRDIR:
 
906
            return True
 
907
        if filename == head:
 
908
            break
 
909
        filename = head
 
910
    return False