/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 integration, mode-changes are broken.

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