~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] update from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
from copy import deepcopy
 
19
from cStringIO import StringIO
 
20
import errno
 
21
import os
18
22
import shutil
19
23
import sys
20
 
import os
21
 
import errno
 
24
from unittest import TestSuite
22
25
from warnings import warn
 
26
try:
 
27
    import xml.sax.saxutils
 
28
except ImportError:
 
29
    raise ImportError("We were unable to import 'xml.sax.saxutils',"
 
30
                      " most likely you have an xml.pyc or xml.pyo file"
 
31
                      " lying around in your bzrlib directory."
 
32
                      " Please remove it.")
23
33
from cStringIO import StringIO
24
34
 
25
35
 
26
36
import bzrlib
27
 
import bzrlib.inventory as inventory
28
 
from bzrlib.trace import mutter, note
29
 
from bzrlib.osutils import (isdir, quotefn,
30
 
                            rename, splitpath, sha_file,
31
 
                            file_kind, abspath, normpath, pathjoin)
 
37
from bzrlib.config import TreeConfig
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
39
from bzrlib.delta import compare_trees
32
40
import bzrlib.errors as errors
33
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
42
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
 
                           DivergedBranches, LockError, UnlistableStore,
 
43
                           DivergedBranches, LockError,
 
44
                           UninitializableFormat,
 
45
                           UnlistableStore,
36
46
                           UnlistableBranch, NoSuchFile, NotVersionedError,
37
47
                           NoWorkingTree)
 
48
import bzrlib.inventory as inventory
 
49
from bzrlib.inventory import Inventory
 
50
from bzrlib.lockable_files import LockableFiles
 
51
from bzrlib.osutils import (isdir, quotefn,
 
52
                            rename, splitpath, sha_file,
 
53
                            file_kind, abspath, normpath, pathjoin,
 
54
                            safe_unicode,
 
55
                            )
38
56
from bzrlib.textui import show_status
39
 
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
40
 
                             NULL_REVISION)
41
 
 
42
 
from bzrlib.delta import compare_trees
 
57
from bzrlib.trace import mutter, note
43
58
from bzrlib.tree import EmptyTree, RevisionTree
44
 
from bzrlib.inventory import Inventory
 
59
from bzrlib.repository import Repository
 
60
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
45
61
from bzrlib.store import copy_all
46
 
from bzrlib.store.text import TextStore
47
 
from bzrlib.store.weave import WeaveStore
48
 
from bzrlib.testament import Testament
 
62
from bzrlib.symbol_versioning import *
49
63
import bzrlib.transactions as transactions
50
64
from bzrlib.transport import Transport, get_transport
 
65
from bzrlib.tree import EmptyTree, RevisionTree
 
66
import bzrlib.ui
51
67
import bzrlib.xml5
52
 
import bzrlib.ui
53
 
from config import TreeConfig
54
68
 
55
69
 
56
70
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
57
71
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
58
72
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
59
 
## TODO: Maybe include checks for common corruption of newlines, etc?
60
 
 
 
73
 
 
74
 
 
75
# TODO: Maybe include checks for common corruption of newlines, etc?
61
76
 
62
77
# TODO: Some operations like log might retrieve the same revisions
63
78
# repeatedly to calculate deltas.  We could perhaps have a weakref
64
79
# cache in memory to make this faster.  In general anything can be
65
 
# cached in memory between lock and unlock operations.
66
 
 
67
 
def find_branch(*ignored, **ignored_too):
68
 
    # XXX: leave this here for about one release, then remove it
69
 
    raise NotImplementedError('find_branch() is not supported anymore, '
70
 
                              'please use one of the new branch constructors')
71
 
 
72
 
 
73
 
def needs_read_lock(unbound):
74
 
    """Decorate unbound to take out and release a read lock."""
75
 
    def decorated(self, *args, **kwargs):
76
 
        self.lock_read()
77
 
        try:
78
 
            return unbound(self, *args, **kwargs)
79
 
        finally:
80
 
            self.unlock()
81
 
    return decorated
82
 
 
83
 
 
84
 
def needs_write_lock(unbound):
85
 
    """Decorate unbound to take out and release a write lock."""
86
 
    def decorated(self, *args, **kwargs):
87
 
        self.lock_write()
88
 
        try:
89
 
            return unbound(self, *args, **kwargs)
90
 
        finally:
91
 
            self.unlock()
92
 
    return decorated
 
80
# cached in memory between lock and unlock operations. .. nb thats
 
81
# what the transaction identity map provides
 
82
 
93
83
 
94
84
######################################################################
95
85
# branch objects
100
90
    base
101
91
        Base directory/url of the branch.
102
92
    """
 
93
    # this is really an instance variable - FIXME move it there
 
94
    # - RBC 20060112
103
95
    base = None
104
96
 
 
97
    _default_initializer = None
 
98
    """The default initializer for making new branches."""
 
99
 
105
100
    def __init__(self, *ignored, **ignored_too):
106
101
        raise NotImplementedError('The Branch class is abstract')
107
102
 
108
103
    @staticmethod
109
104
    def open_downlevel(base):
110
 
        """Open a branch which may be of an old format.
111
 
        
112
 
        Only local branches are supported."""
113
 
        return BzrBranch(get_transport(base), relax_version_check=True)
 
105
        """Open a branch which may be of an old format."""
 
106
        return Branch.open(base, _unsupported=True)
114
107
        
115
108
    @staticmethod
116
 
    def open(base):
117
 
        """Open an existing branch, rooted at 'base' (url)"""
 
109
    def open(base, _unsupported=False):
 
110
        """Open an existing branch, rooted at 'base' (url)
 
111
        
 
112
        _unsupported is a private parameter to the Branch class.
 
113
        """
118
114
        t = get_transport(base)
119
115
        mutter("trying to open %r with transport %r", base, t)
120
 
        return BzrBranch(t)
 
116
        format = BzrBranchFormat.find_format(t)
 
117
        if not _unsupported and not format.is_supported():
 
118
            # see open_downlevel to open legacy branches.
 
119
            raise errors.UnsupportedFormatError(
 
120
                    'sorry, branch format %s not supported' % format,
 
121
                    ['use a different bzr version',
 
122
                     'or remove the .bzr directory'
 
123
                     ' and "bzr init" again'])
 
124
        return format.open(t)
121
125
 
122
126
    @staticmethod
123
127
    def open_containing(url):
127
131
 
128
132
        Basically we keep looking up until we find the control directory or
129
133
        run into the root.  If there isn't one, raises NotBranchError.
 
134
        If there is one and it is either an unrecognised format or an unsupported 
 
135
        format, UnknownFormatError or UnsupportedFormatError are raised.
130
136
        If there is one, it is returned, along with the unused portion of url.
131
137
        """
132
138
        t = get_transport(url)
 
139
        # this gets the normalised url back. I.e. '.' -> the full path.
 
140
        url = t.base
133
141
        while True:
134
142
            try:
135
 
                return BzrBranch(t), t.relpath(url)
 
143
                format = BzrBranchFormat.find_format(t)
 
144
                return format.open(t), t.relpath(url)
136
145
            except NotBranchError, e:
137
146
                mutter('not a branch in: %r %s', t.base, e)
138
147
            new_t = t.clone('..')
142
151
            t = new_t
143
152
 
144
153
    @staticmethod
 
154
    def create(base):
 
155
        """Create a new Branch at the url 'bzr'.
 
156
        
 
157
        This will call the current default initializer with base
 
158
        as the only parameter.
 
159
        """
 
160
        return Branch._default_initializer(safe_unicode(base))
 
161
 
 
162
    @staticmethod
 
163
    @deprecated_function(zero_eight)
145
164
    def initialize(base):
146
 
        """Create a new branch, rooted at 'base' (url)"""
147
 
        t = get_transport(base)
148
 
        return BzrBranch(t, init=True)
 
165
        """Create a new working tree and branch, rooted at 'base' (url)
 
166
 
 
167
        NOTE: This will soon be deprecated in favour of creation
 
168
        through a BzrDir.
 
169
        """
 
170
        # imported here to prevent scope creep as this is going.
 
171
        from bzrlib.workingtree import WorkingTree
 
172
        return WorkingTree.create_standalone(safe_unicode(base)).branch
 
173
 
 
174
    @staticmethod
 
175
    def get_default_initializer():
 
176
        """Return the initializer being used for new branches."""
 
177
        return Branch._default_initializer
 
178
 
 
179
    @staticmethod
 
180
    def set_default_initializer(initializer):
 
181
        """Set the initializer to be used for new branches."""
 
182
        Branch._default_initializer = staticmethod(initializer)
149
183
 
150
184
    def setup_caching(self, cache_root):
151
185
        """Subclasses that care about caching should override this, and set
152
186
        up cached stores located under cache_root.
153
187
        """
 
188
        # seems to be unused, 2006-01-13 mbp
 
189
        warn('%s is deprecated' % self.setup_caching)
154
190
        self.cache_root = cache_root
155
191
 
156
192
    def _get_nick(self):
157
193
        cfg = self.tree_config()
158
 
        return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
 
194
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
159
195
 
160
196
    def _set_nick(self, nick):
161
197
        cfg = self.tree_config()
168
204
        """Copy the content of this branches store to branch_to."""
169
205
        raise NotImplementedError('push_stores is abstract')
170
206
 
171
 
    def get_transaction(self):
172
 
        """Return the current active transaction.
173
 
 
174
 
        If no transaction is active, this returns a passthrough object
175
 
        for which all data is immediately flushed and no caching happens.
176
 
        """
177
 
        raise NotImplementedError('get_transaction is abstract')
178
 
 
179
207
    def lock_write(self):
180
208
        raise NotImplementedError('lock_write is abstract')
181
209
        
185
213
    def unlock(self):
186
214
        raise NotImplementedError('unlock is abstract')
187
215
 
 
216
    def peek_lock_mode(self):
 
217
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
218
        raise NotImplementedError(self.peek_lock_mode)
 
219
 
188
220
    def abspath(self, name):
189
221
        """Return absolute filename for something in the branch
190
222
        
193
225
        """
194
226
        raise NotImplementedError('abspath is abstract')
195
227
 
196
 
    def controlfilename(self, file_or_path):
197
 
        """Return location relative to branch."""
198
 
        raise NotImplementedError('controlfilename is abstract')
199
 
 
200
 
    def controlfile(self, file_or_path, mode='r'):
201
 
        """Open a control file for this branch.
202
 
 
203
 
        There are two classes of file in the control directory: text
204
 
        and binary.  binary files are untranslated byte streams.  Text
205
 
        control files are stored with Unix newlines and in UTF-8, even
206
 
        if the platform or locale defaults are different.
207
 
 
208
 
        Controlfiles should almost never be opened in write mode but
209
 
        rather should be atomically copied and replaced using atomicfile.
210
 
        """
211
 
        raise NotImplementedError('controlfile is abstract')
212
 
 
213
 
    def put_controlfile(self, path, f, encode=True):
214
 
        """Write an entry as a controlfile.
215
 
 
216
 
        :param path: The path to put the file, relative to the .bzr control
217
 
                     directory
218
 
        :param f: A file-like or string object whose contents should be copied.
219
 
        :param encode:  If true, encode the contents as utf-8
220
 
        """
221
 
        raise NotImplementedError('put_controlfile is abstract')
222
 
 
223
 
    def put_controlfiles(self, files, encode=True):
224
 
        """Write several entries as controlfiles.
225
 
 
226
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
227
 
                      underneath the bzr control directory
228
 
        :param encode:  If true, encode the contents as utf-8
229
 
        """
230
 
        raise NotImplementedError('put_controlfiles is abstract')
231
 
 
232
228
    def get_root_id(self):
233
229
        """Return the id of this branches root"""
234
230
        raise NotImplementedError('get_root_id is abstract')
235
231
 
236
 
    def set_root_id(self, file_id):
237
 
        raise NotImplementedError('set_root_id is abstract')
238
 
 
239
232
    def print_file(self, file, revision_id):
240
233
        """Print `file` to stdout."""
241
234
        raise NotImplementedError('print_file is abstract')
246
239
    def set_revision_history(self, rev_history):
247
240
        raise NotImplementedError('set_revision_history is abstract')
248
241
 
249
 
    def has_revision(self, revision_id):
250
 
        """True if this branch has a copy of the revision.
251
 
 
252
 
        This does not necessarily imply the revision is merge
253
 
        or on the mainline."""
254
 
        raise NotImplementedError('has_revision is abstract')
255
 
 
256
 
    def get_revision_xml(self, revision_id):
257
 
        raise NotImplementedError('get_revision_xml is abstract')
258
 
 
259
 
    def get_revision(self, revision_id):
260
 
        """Return the Revision object for a named revision"""
261
 
        raise NotImplementedError('get_revision is abstract')
262
 
 
263
 
    def get_revision_delta(self, revno):
264
 
        """Return the delta for one revision.
265
 
 
266
 
        The delta is relative to its mainline predecessor, or the
267
 
        empty tree for revision 1.
268
 
        """
269
 
        assert isinstance(revno, int)
270
 
        rh = self.revision_history()
271
 
        if not (1 <= revno <= len(rh)):
272
 
            raise InvalidRevisionNumber(revno)
273
 
 
274
 
        # revno is 1-based; list is 0-based
275
 
 
276
 
        new_tree = self.revision_tree(rh[revno-1])
277
 
        if revno == 1:
278
 
            old_tree = EmptyTree()
279
 
        else:
280
 
            old_tree = self.revision_tree(rh[revno-2])
281
 
 
282
 
        return compare_trees(old_tree, new_tree)
283
 
 
284
 
    def get_revision_sha1(self, revision_id):
285
 
        """Hash the stored value of a revision, and return it."""
286
 
        raise NotImplementedError('get_revision_sha1 is abstract')
287
 
 
288
 
    def get_ancestry(self, revision_id):
289
 
        """Return a list of revision-ids integrated by a revision.
290
 
        
291
 
        This currently returns a list, but the ordering is not guaranteed:
292
 
        treat it as a set.
293
 
        """
294
 
        raise NotImplementedError('get_ancestry is abstract')
295
 
 
296
 
    def get_inventory(self, revision_id):
297
 
        """Get Inventory object by hash."""
298
 
        raise NotImplementedError('get_inventory is abstract')
299
 
 
300
 
    def get_inventory_xml(self, revision_id):
301
 
        """Get inventory XML as a file object."""
302
 
        raise NotImplementedError('get_inventory_xml is abstract')
303
 
 
304
 
    def get_inventory_sha1(self, revision_id):
305
 
        """Return the sha1 hash of the inventory entry."""
306
 
        raise NotImplementedError('get_inventory_sha1 is abstract')
307
 
 
308
 
    def get_revision_inventory(self, revision_id):
309
 
        """Return inventory of a past revision."""
310
 
        raise NotImplementedError('get_revision_inventory is abstract')
311
 
 
312
242
    def revision_history(self):
313
243
        """Return sequence of revision hashes on to this branch."""
314
244
        raise NotImplementedError('revision_history is abstract')
335
265
        If self and other have not diverged, return a list of the revisions
336
266
        present in other, but missing from self.
337
267
 
338
 
        >>> from bzrlib.commit import commit
339
268
        >>> bzrlib.trace.silent = True
340
269
        >>> br1 = ScratchBranch()
341
270
        >>> br2 = ScratchBranch()
342
271
        >>> br1.missing_revisions(br2)
343
272
        []
344
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
273
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-1")
345
274
        >>> br1.missing_revisions(br2)
346
275
        [u'REVISION-ID-1']
347
276
        >>> br2.missing_revisions(br1)
348
277
        []
349
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
278
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-1")
350
279
        >>> br1.missing_revisions(br2)
351
280
        []
352
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
281
        >>> br2.working_tree().commit("lala!", rev_id="REVISION-ID-2A")
353
282
        >>> br1.missing_revisions(br2)
354
283
        [u'REVISION-ID-2A']
355
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
284
        >>> br1.working_tree().commit("lala!", rev_id="REVISION-ID-2B")
356
285
        >>> br1.missing_revisions(br2)
357
286
        Traceback (most recent call last):
358
287
        DivergedBranches: These branches have diverged.  Try merge.
373
302
            if stop_revision > other_len:
374
303
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
375
304
        return other_history[self_len:stop_revision]
376
 
    
 
305
 
377
306
    def update_revisions(self, other, stop_revision=None):
378
307
        """Pull in new perfect-fit revisions."""
379
308
        raise NotImplementedError('update_revisions is abstract')
401
330
            raise bzrlib.errors.NoSuchRevision(self, revno)
402
331
        return history[revno - 1]
403
332
 
404
 
    def revision_tree(self, revision_id):
405
 
        """Return Tree for a revision on this branch.
406
 
 
407
 
        `revision_id` may be None for the null revision, in which case
408
 
        an `EmptyTree` is returned."""
409
 
        raise NotImplementedError('revision_tree is abstract')
410
 
 
411
333
    def working_tree(self):
412
334
        """Return a `Tree` for the working copy if this is a local branch."""
413
335
        raise NotImplementedError('working_tree is abstract')
420
342
 
421
343
        If there are no revisions yet, return an `EmptyTree`.
422
344
        """
423
 
        return self.revision_tree(self.last_revision())
 
345
        return self.repository.revision_tree(self.last_revision())
424
346
 
425
347
    def rename_one(self, from_rel, to_rel):
426
348
        """Rename one file.
481
403
        if revno < 1 or revno > self.revno():
482
404
            raise InvalidRevisionNumber(revno)
483
405
        
484
 
    def sign_revision(self, revision_id, gpg_strategy):
485
 
        raise NotImplementedError('sign_revision is abstract')
486
 
 
487
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
 
        raise NotImplementedError('store_revision_signature is abstract')
 
406
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
407
        """Copy this branch into the existing directory to_location.
 
408
 
 
409
        Returns the newly created branch object.
 
410
 
 
411
        revision
 
412
            If not None, only revisions up to this point will be copied.
 
413
            The head of the new branch will be that revision.  Must be a
 
414
            revid or None.
 
415
    
 
416
        to_location -- The destination directory; must either exist and be 
 
417
            empty, or not exist, in which case it is created.
 
418
    
 
419
        basis_branch
 
420
            A local branch to copy revisions from, related to this branch. 
 
421
            This is used when branching from a remote (slow) branch, and we have
 
422
            a local branch that might contain some relevant revisions.
 
423
    
 
424
        to_branch_type
 
425
            Branch type of destination branch
 
426
        """
 
427
        from bzrlib.workingtree import WorkingTree
 
428
        assert isinstance(to_location, basestring)
 
429
        if not bzrlib.osutils.lexists(to_location):
 
430
            os.mkdir(to_location)
 
431
        if to_branch_type is None:
 
432
            to_branch_type = BzrBranch
 
433
        print "FIXME use a branch format here"
 
434
        br_to = to_branch_type.initialize(to_location)
 
435
        mutter("copy branch from %s to %s", self, br_to)
 
436
        if basis_branch is not None:
 
437
            basis_branch.push_stores(br_to)
 
438
        if revision is None:
 
439
            revision = self.last_revision()
 
440
        br_to.update_revisions(self, stop_revision=revision)
 
441
        br_to.set_parent(self.base)
 
442
        WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
 
443
        mutter("copied")
 
444
        return br_to
 
445
 
 
446
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
447
        """ This function returns the file_id(s) involved in the
 
448
            changes between the from_revid revision and the to_revid
 
449
            revision
 
450
        """
 
451
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
452
 
 
453
    def fileid_involved(self, last_revid=None):
 
454
        """ This function returns the file_id(s) involved in the
 
455
            changes up to the revision last_revid
 
456
            If no parametr is passed, then all file_id[s] present in the
 
457
            repository are returned
 
458
        """
 
459
        raise NotImplementedError('fileid_involved is abstract')
 
460
 
 
461
    def fileid_involved_by_set(self, changes):
 
462
        """ This function returns the file_id(s) involved in the
 
463
            changes present in the set 'changes'
 
464
        """
 
465
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
466
 
 
467
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
468
        """ This function returns the file_id(s) involved in the
 
469
            changes between the from_revid revision and the to_revid
 
470
            revision
 
471
        """
 
472
        raise NotImplementedError('fileid_involved_between_revs is abstract')
 
473
 
 
474
    def fileid_involved(self, last_revid=None):
 
475
        """ This function returns the file_id(s) involved in the
 
476
            changes up to the revision last_revid
 
477
            If no parametr is passed, then all file_id[s] present in the
 
478
            repository are returned
 
479
        """
 
480
        raise NotImplementedError('fileid_involved is abstract')
 
481
 
 
482
    def fileid_involved_by_set(self, changes):
 
483
        """ This function returns the file_id(s) involved in the
 
484
            changes present in the set 'changes'
 
485
        """
 
486
        raise NotImplementedError('fileid_involved_by_set is abstract')
 
487
 
 
488
class BzrBranchFormat(object):
 
489
    """An encapsulation of the initialization and open routines for a format.
 
490
 
 
491
    Formats provide three things:
 
492
     * An initialization routine,
 
493
     * a format string,
 
494
     * an open routine.
 
495
 
 
496
    Formats are placed in an dict by their format string for reference 
 
497
    during branch opening. Its not required that these be instances, they
 
498
    can be classes themselves with class methods - it simply depends on 
 
499
    whether state is needed for a given format or not.
 
500
 
 
501
    Once a format is deprecated, just deprecate the initialize and open
 
502
    methods on the format class. Do not deprecate the object, as the 
 
503
    object will be created every time regardless.
 
504
    """
 
505
 
 
506
    _formats = {}
 
507
    """The known formats."""
 
508
 
 
509
    @classmethod
 
510
    def find_format(klass, transport):
 
511
        """Return the format registered for URL."""
 
512
        try:
 
513
            format_string = transport.get(".bzr/branch-format").read()
 
514
            return klass._formats[format_string]
 
515
        except NoSuchFile:
 
516
            raise NotBranchError(path=transport.base)
 
517
        except KeyError:
 
518
            raise errors.UnknownFormatError(format_string)
 
519
 
 
520
    def get_format_string(self):
 
521
        """Return the ASCII format string that identifies this format."""
 
522
        raise NotImplementedError(self.get_format_string)
 
523
 
 
524
    def _find_modes(self, t):
 
525
        """Determine the appropriate modes for files and directories.
 
526
        
 
527
        FIXME: When this merges into, or from storage,
 
528
        this code becomes delgatable to a LockableFiles instance.
 
529
 
 
530
        For now its cribbed and returns (dir_mode, file_mode)
 
531
        """
 
532
        try:
 
533
            st = t.stat('.')
 
534
        except errors.TransportNotPossible:
 
535
            dir_mode = 0755
 
536
            file_mode = 0644
 
537
        else:
 
538
            dir_mode = st.st_mode & 07777
 
539
            # Remove the sticky and execute bits for files
 
540
            file_mode = dir_mode & ~07111
 
541
        if not BzrBranch._set_dir_mode:
 
542
            dir_mode = None
 
543
        if not BzrBranch._set_file_mode:
 
544
            file_mode = None
 
545
        return dir_mode, file_mode
 
546
 
 
547
    def initialize(self, url):
 
548
        """Create a branch of this format at url and return an open branch."""
 
549
        t = get_transport(url)
 
550
        from bzrlib.weavefile import write_weave_v5
 
551
        from bzrlib.weave import Weave
 
552
        
 
553
        # Create an empty weave
 
554
        sio = StringIO()
 
555
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
556
        empty_weave = sio.getvalue()
 
557
 
 
558
        # Since we don't have a .bzr directory, inherit the
 
559
        # mode from the root directory
 
560
        temp_control = LockableFiles(t, '')
 
561
        temp_control._transport.mkdir('.bzr',
 
562
                                      mode=temp_control._dir_mode)
 
563
        file_mode = temp_control._file_mode
 
564
        del temp_control
 
565
        mutter('created control directory in ' + t.base)
 
566
        control = t.clone('.bzr')
 
567
        dirs = ['revision-store', 'weaves']
 
568
        lock_file = 'branch-lock'
 
569
        utf8_files = [('README', 
 
570
                       "This is a Bazaar-NG control directory.\n"
 
571
                       "Do not change any files in this directory.\n"),
 
572
                      ('branch-format', self.get_format_string()),
 
573
                      ('revision-history', ''),
 
574
                      ('branch-name', ''),
 
575
                      ]
 
576
        files = [('inventory.weave', StringIO(empty_weave)), 
 
577
                 ]
 
578
        
 
579
        # FIXME: RBC 20060125 dont peek under the covers
 
580
        # NB: no need to escape relative paths that are url safe.
 
581
        control.put(lock_file, StringIO(), mode=file_mode)
 
582
        control_files = LockableFiles(control, lock_file)
 
583
        control_files.lock_write()
 
584
        control_files._transport.mkdir_multi(dirs,
 
585
                mode=control_files._dir_mode)
 
586
        try:
 
587
            for file, content in utf8_files:
 
588
                control_files.put_utf8(file, content)
 
589
            for file, content in files:
 
590
                control_files.put(file, content)
 
591
        finally:
 
592
            control_files.unlock()
 
593
        return BzrBranch(t, _format=self, _control_files=control_files)
 
594
 
 
595
    def is_supported(self):
 
596
        """Is this format supported?
 
597
 
 
598
        Supported formats can be initialized and opened.
 
599
        Unsupported formats may not support initialization or committing or 
 
600
        some other features depending on the reason for not being supported.
 
601
        """
 
602
        return True
 
603
 
 
604
    def open(self, transport):
 
605
        """Fill out the data in branch for the branch at url."""
 
606
        return BzrBranch(transport, _format=self)
 
607
 
 
608
    @classmethod
 
609
    def register_format(klass, format):
 
610
        klass._formats[format.get_format_string()] = format
 
611
 
 
612
    @classmethod
 
613
    def unregister_format(klass, format):
 
614
        assert klass._formats[format.get_format_string()] is format
 
615
        del klass._formats[format.get_format_string()]
 
616
 
 
617
 
 
618
class BzrBranchFormat4(BzrBranchFormat):
 
619
    """Bzr branch format 4.
 
620
 
 
621
    This format has:
 
622
     - flat stores
 
623
     - TextStores for texts, inventories,revisions.
 
624
 
 
625
    This format is deprecated: it indexes texts using a text it which is
 
626
    removed in format 5; write support for this format has been removed.
 
627
    """
 
628
 
 
629
    def get_format_string(self):
 
630
        """See BzrBranchFormat.get_format_string()."""
 
631
        return BZR_BRANCH_FORMAT_4
 
632
 
 
633
    def initialize(self, url):
 
634
        """Format 4 branches cannot be created."""
 
635
        raise UninitializableFormat(self)
 
636
 
 
637
    def is_supported(self):
 
638
        """Format 4 is not supported.
 
639
 
 
640
        It is not supported because the model changed from 4 to 5 and the
 
641
        conversion logic is expensive - so doing it on the fly was not 
 
642
        feasible.
 
643
        """
 
644
        return False
 
645
 
 
646
 
 
647
class BzrBranchFormat5(BzrBranchFormat):
 
648
    """Bzr branch format 5.
 
649
 
 
650
    This format has:
 
651
     - weaves for file texts and inventory
 
652
     - flat stores
 
653
     - TextStores for revisions and signatures.
 
654
    """
 
655
 
 
656
    def get_format_string(self):
 
657
        """See BzrBranchFormat.get_format_string()."""
 
658
        return BZR_BRANCH_FORMAT_5
 
659
 
 
660
 
 
661
class BzrBranchFormat6(BzrBranchFormat):
 
662
    """Bzr branch format 6.
 
663
 
 
664
    This format has:
 
665
     - weaves for file texts and inventory
 
666
     - hash subdirectory based stores.
 
667
     - TextStores for revisions and signatures.
 
668
    """
 
669
 
 
670
    def get_format_string(self):
 
671
        """See BzrBranchFormat.get_format_string()."""
 
672
        return BZR_BRANCH_FORMAT_6
 
673
 
 
674
 
 
675
BzrBranchFormat.register_format(BzrBranchFormat4())
 
676
BzrBranchFormat.register_format(BzrBranchFormat5())
 
677
BzrBranchFormat.register_format(BzrBranchFormat6())
 
678
 
 
679
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
 
680
#       make sure that ancestry.weave is deleted (it is never used, but
 
681
#       used to be created)
 
682
 
489
683
 
490
684
class BzrBranch(Branch):
491
685
    """A branch stored in the actual filesystem.
494
688
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
495
689
    it's writable, and can be accessed via the normal filesystem API.
496
690
 
497
 
    _lock_mode
498
 
        None, or 'r' or 'w'
499
 
 
500
 
    _lock_count
501
 
        If _lock_mode is true, a positive count of the number of times the
502
 
        lock has been taken.
503
 
 
504
 
    _lock
505
 
        Lock object from bzrlib.lock.
506
691
    """
507
692
    # We actually expect this class to be somewhat short-lived; part of its
508
693
    # purpose is to try to isolate what bits of the branch logic are tied to
509
694
    # filesystem access, so that in a later step, we can extricate them to
510
695
    # a separarte ("storage") class.
511
 
    _lock_mode = None
512
 
    _lock_count = None
513
 
    _lock = None
514
696
    _inventory_weave = None
515
 
    # If set to False (by a plugin, etc) BzrBranch will not set the
516
 
    # mode on created files or directories
517
 
    _set_file_mode = True
518
 
    _set_dir_mode = True
519
697
    
520
698
    # Map some sort of prefix into a namespace
521
699
    # stuff like "revno:10", "revid:", etc.
524
702
 
525
703
    def push_stores(self, branch_to):
526
704
        """See Branch.push_stores."""
527
 
        if (self._branch_format != branch_to._branch_format
528
 
            or self._branch_format != 4):
 
705
        if (not isinstance(self._branch_format, BzrBranchFormat4) or
 
706
            self._branch_format != branch_to._branch_format):
529
707
            from bzrlib.fetch import greedy_fetch
530
 
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
708
            mutter("Using fetch logic to push between %s(%s) and %s(%s)",
531
709
                   self, self._branch_format, branch_to, branch_to._branch_format)
532
710
            greedy_fetch(to_branch=branch_to, from_branch=self,
533
711
                         revision=self.last_revision())
534
712
            return
535
713
 
 
714
        # format 4 to format 4 logic only.
536
715
        store_pairs = ((self.text_store,      branch_to.text_store),
537
716
                       (self.inventory_store, branch_to.inventory_store),
538
717
                       (self.revision_store,  branch_to.revision_store))
542
721
        except UnlistableStore:
543
722
            raise UnlistableBranch(from_store)
544
723
 
545
 
    def __init__(self, transport, init=False,
546
 
                 relax_version_check=False):
 
724
    def __init__(self, transport, init=DEPRECATED_PARAMETER,
 
725
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
726
                 _control_files=None):
547
727
        """Create new branch object at a particular location.
548
728
 
549
729
        transport -- A Transport object, defining how to access files.
563
743
        assert isinstance(transport, Transport), \
564
744
            "%r is not a Transport" % transport
565
745
        self._transport = transport
566
 
        if init:
567
 
            self._make_control()
568
 
        self._check_format(relax_version_check)
569
 
        self._find_modes()
570
 
 
571
 
        def get_store(name, compressed=True, prefixed=False):
572
 
            relpath = self._rel_controlfilename(unicode(name))
573
 
            store = TextStore(self._transport.clone(relpath),
574
 
                              dir_mode=self._dir_mode,
575
 
                              file_mode=self._file_mode,
576
 
                              prefixed=prefixed,
577
 
                              compressed=compressed)
578
 
            return store
579
 
 
580
 
        def get_weave(name, prefixed=False):
581
 
            relpath = self._rel_controlfilename(unicode(name))
582
 
            ws = WeaveStore(self._transport.clone(relpath),
583
 
                            prefixed=prefixed,
584
 
                            dir_mode=self._dir_mode,
585
 
                            file_mode=self._file_mode)
586
 
            if self._transport.should_cache():
587
 
                ws.enable_cache = True
588
 
            return ws
589
 
 
590
 
        if self._branch_format == 4:
591
 
            self.inventory_store = get_store('inventory-store')
592
 
            self.text_store = get_store('text-store')
593
 
            self.revision_store = get_store('revision-store')
594
 
        elif self._branch_format == 5:
595
 
            self.control_weaves = get_weave(u'')
596
 
            self.weave_store = get_weave(u'weaves')
597
 
            self.revision_store = get_store(u'revision-store', compressed=False)
598
 
        elif self._branch_format == 6:
599
 
            self.control_weaves = get_weave(u'')
600
 
            self.weave_store = get_weave(u'weaves', prefixed=True)
601
 
            self.revision_store = get_store(u'revision-store', compressed=False,
602
 
                                            prefixed=True)
603
 
        self.revision_store.register_suffix('sig')
604
 
        self._transaction = None
 
746
        self._base = self._transport.base
 
747
        if _control_files is None:
 
748
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
 
749
                                           'branch-lock')
 
750
        self.control_files = _control_files
 
751
        if deprecated_passed(init):
 
752
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
753
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
754
                 DeprecationWarning,
 
755
                 stacklevel=2)
 
756
            if init:
 
757
                # this is slower than before deprecation, oh well never mind.
 
758
                # -> its deprecated.
 
759
                self._initialize(transport.base)
 
760
        self._check_format(_format)
 
761
        if deprecated_passed(relax_version_check):
 
762
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
763
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
764
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
765
                 "open() method.",
 
766
                 DeprecationWarning,
 
767
                 stacklevel=2)
 
768
            if (not relax_version_check
 
769
                and not self._branch_format.is_supported()):
 
770
                raise errors.UnsupportedFormatError(
 
771
                        'sorry, branch format %r not supported' % fmt,
 
772
                        ['use a different bzr version',
 
773
                         'or remove the .bzr directory'
 
774
                         ' and "bzr init" again'])
 
775
        self.repository = Repository(transport, self._branch_format)
 
776
 
 
777
 
 
778
    @staticmethod
 
779
    def _initialize(base):
 
780
        """Create a bzr branch in the latest format."""
 
781
        return BzrBranchFormat6().initialize(base)
605
782
 
606
783
    def __str__(self):
607
 
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
 
784
        return '%s(%r)' % (self.__class__.__name__, self.base)
608
785
 
609
786
    __repr__ = __str__
610
787
 
611
788
    def __del__(self):
612
 
        if self._lock_mode or self._lock:
613
 
            # XXX: This should show something every time, and be suitable for
614
 
            # headless operation and embedding
615
 
            warn("branch %r was not explicitly unlocked" % self)
616
 
            self._lock.unlock()
617
 
 
618
789
        # TODO: It might be best to do this somewhere else,
619
790
        # but it is nice for a Branch object to automatically
620
791
        # cache it's information.
621
792
        # Alternatively, we could have the Transport objects cache requests
622
793
        # See the earlier discussion about how major objects (like Branch)
623
794
        # should never expect their __del__ function to run.
 
795
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
624
796
        if hasattr(self, 'cache_root') and self.cache_root is not None:
625
797
            try:
626
798
                shutil.rmtree(self.cache_root)
629
801
            self.cache_root = None
630
802
 
631
803
    def _get_base(self):
632
 
        if self._transport:
633
 
            return self._transport.base
634
 
        return None
 
804
        return self._base
635
805
 
636
806
    base = property(_get_base, doc="The URL for the root of this branch.")
637
807
 
638
808
    def _finish_transaction(self):
639
809
        """Exit the current transaction."""
640
 
        if self._transaction is None:
641
 
            raise errors.LockError('Branch %s is not in a transaction' %
642
 
                                   self)
643
 
        transaction = self._transaction
644
 
        self._transaction = None
645
 
        transaction.finish()
 
810
        return self.control_files._finish_transaction()
646
811
 
647
812
    def get_transaction(self):
648
 
        """See Branch.get_transaction."""
649
 
        if self._transaction is None:
650
 
            return transactions.PassThroughTransaction()
651
 
        else:
652
 
            return self._transaction
653
 
 
654
 
    def _set_transaction(self, new_transaction):
 
813
        """Return the current active transaction.
 
814
 
 
815
        If no transaction is active, this returns a passthrough object
 
816
        for which all data is immediately flushed and no caching happens.
 
817
        """
 
818
        # this is an explicit function so that we can do tricky stuff
 
819
        # when the storage in rev_storage is elsewhere.
 
820
        # we probably need to hook the two 'lock a location' and 
 
821
        # 'have a transaction' together more delicately, so that
 
822
        # we can have two locks (branch and storage) and one transaction
 
823
        # ... and finishing the transaction unlocks both, but unlocking
 
824
        # does not. - RBC 20051121
 
825
        return self.control_files.get_transaction()
 
826
 
 
827
    def _set_transaction(self, transaction):
655
828
        """Set a new active transaction."""
656
 
        if self._transaction is not None:
657
 
            raise errors.LockError('Branch %s is in a transaction already.' %
658
 
                                   self)
659
 
        self._transaction = new_transaction
660
 
 
661
 
    def lock_write(self):
662
 
        #mutter("lock write: %s (%s)", self, self._lock_count)
663
 
        # TODO: Upgrade locking to support using a Transport,
664
 
        # and potentially a remote locking protocol
665
 
        if self._lock_mode:
666
 
            if self._lock_mode != 'w':
667
 
                raise LockError("can't upgrade to a write lock from %r" %
668
 
                                self._lock_mode)
669
 
            self._lock_count += 1
670
 
        else:
671
 
            self._lock = self._transport.lock_write(
672
 
                    self._rel_controlfilename('branch-lock'))
673
 
            self._lock_mode = 'w'
674
 
            self._lock_count = 1
675
 
            self._set_transaction(transactions.PassThroughTransaction())
676
 
 
677
 
    def lock_read(self):
678
 
        #mutter("lock read: %s (%s)", self, self._lock_count)
679
 
        if self._lock_mode:
680
 
            assert self._lock_mode in ('r', 'w'), \
681
 
                   "invalid lock mode %r" % self._lock_mode
682
 
            self._lock_count += 1
683
 
        else:
684
 
            self._lock = self._transport.lock_read(
685
 
                    self._rel_controlfilename('branch-lock'))
686
 
            self._lock_mode = 'r'
687
 
            self._lock_count = 1
688
 
            self._set_transaction(transactions.ReadOnlyTransaction())
689
 
            # 5K may be excessive, but hey, its a knob.
690
 
            self.get_transaction().set_cache_size(5000)
691
 
                        
692
 
    def unlock(self):
693
 
        #mutter("unlock: %s (%s)", self, self._lock_count)
694
 
        if not self._lock_mode:
695
 
            raise LockError('branch %r is not locked' % (self))
696
 
 
697
 
        if self._lock_count > 1:
698
 
            self._lock_count -= 1
699
 
        else:
700
 
            self._finish_transaction()
701
 
            self._lock.unlock()
702
 
            self._lock = None
703
 
            self._lock_mode = self._lock_count = None
 
829
        return self.control_files._set_transaction(transaction)
704
830
 
705
831
    def abspath(self, name):
706
832
        """See Branch.abspath."""
707
 
        return self._transport.abspath(name)
708
 
 
709
 
    def _rel_controlfilename(self, file_or_path):
710
 
        if not isinstance(file_or_path, basestring):
711
 
            file_or_path = u'/'.join(file_or_path)
712
 
        if file_or_path == '':
713
 
            return bzrlib.BZRDIR
714
 
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
715
 
 
716
 
    def controlfilename(self, file_or_path):
717
 
        """See Branch.controlfilename."""
718
 
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
719
 
 
720
 
    def controlfile(self, file_or_path, mode='r'):
721
 
        """See Branch.controlfile."""
722
 
        import codecs
723
 
 
724
 
        relpath = self._rel_controlfilename(file_or_path)
725
 
        #TODO: codecs.open() buffers linewise, so it was overloaded with
726
 
        # a much larger buffer, do we need to do the same for getreader/getwriter?
727
 
        if mode == 'rb': 
728
 
            return self._transport.get(relpath)
729
 
        elif mode == 'wb':
730
 
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
731
 
        elif mode == 'r':
732
 
            # XXX: Do we really want errors='replace'?   Perhaps it should be
733
 
            # an error, or at least reported, if there's incorrectly-encoded
734
 
            # data inside a file.
735
 
            # <https://launchpad.net/products/bzr/+bug/3823>
736
 
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
737
 
        elif mode == 'w':
738
 
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
739
 
        else:
740
 
            raise BzrError("invalid controlfile mode %r" % mode)
741
 
 
742
 
    def put_controlfile(self, path, f, encode=True):
743
 
        """See Branch.put_controlfile."""
744
 
        self.put_controlfiles([(path, f)], encode=encode)
745
 
 
746
 
    def put_controlfiles(self, files, encode=True):
747
 
        """See Branch.put_controlfiles."""
748
 
        import codecs
749
 
        ctrl_files = []
750
 
        for path, f in files:
751
 
            if encode:
752
 
                if isinstance(f, basestring):
753
 
                    f = f.encode('utf-8', 'replace')
754
 
                else:
755
 
                    f = codecs.getwriter('utf-8')(f, errors='replace')
756
 
            path = self._rel_controlfilename(path)
757
 
            ctrl_files.append((path, f))
758
 
        self._transport.put_multi(ctrl_files, mode=self._file_mode)
759
 
 
760
 
    def _find_modes(self, path=None):
761
 
        """Determine the appropriate modes for files and directories."""
762
 
        try:
763
 
            if path is None:
764
 
                path = self._rel_controlfilename('')
765
 
            st = self._transport.stat(path)
766
 
        except errors.TransportNotPossible:
767
 
            self._dir_mode = 0755
768
 
            self._file_mode = 0644
769
 
        else:
770
 
            self._dir_mode = st.st_mode & 07777
771
 
            # Remove the sticky and execute bits for files
772
 
            self._file_mode = self._dir_mode & ~07111
773
 
        if not self._set_dir_mode:
774
 
            self._dir_mode = None
775
 
        if not self._set_file_mode:
776
 
            self._file_mode = None
777
 
 
778
 
    def _make_control(self):
779
 
        from bzrlib.inventory import Inventory
780
 
        from bzrlib.weavefile import write_weave_v5
781
 
        from bzrlib.weave import Weave
782
 
        
783
 
        # Create an empty inventory
784
 
        sio = StringIO()
785
 
        # if we want per-tree root ids then this is the place to set
786
 
        # them; they're not needed for now and so ommitted for
787
 
        # simplicity.
788
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
789
 
        empty_inv = sio.getvalue()
790
 
        sio = StringIO()
791
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
792
 
        empty_weave = sio.getvalue()
793
 
 
794
 
        cfn = self._rel_controlfilename
795
 
        # Since we don't have a .bzr directory, inherit the
796
 
        # mode from the root directory
797
 
        self._find_modes(u'.')
798
 
 
799
 
        dirs = ['', 'revision-store', 'weaves']
800
 
        files = [('README', 
801
 
            "This is a Bazaar-NG control directory.\n"
802
 
            "Do not change any files in this directory.\n"),
803
 
            ('branch-format', BZR_BRANCH_FORMAT_6),
804
 
            ('revision-history', ''),
805
 
            ('branch-name', ''),
806
 
            ('branch-lock', ''),
807
 
            ('pending-merges', ''),
808
 
            ('inventory', empty_inv),
809
 
            ('inventory.weave', empty_weave),
810
 
            ('ancestry.weave', empty_weave)
811
 
        ]
812
 
        self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
813
 
        self.put_controlfiles(files)
814
 
        mutter('created control directory in ' + self._transport.base)
815
 
 
816
 
    def _check_format(self, relax_version_check):
817
 
        """Check this branch format is supported.
818
 
 
819
 
        The format level is stored, as an integer, in
 
833
        return self.control_files._transport.abspath(name)
 
834
 
 
835
    def _check_format(self, format):
 
836
        """Identify the branch format if needed.
 
837
 
 
838
        The format is stored as a reference to the format object in
820
839
        self._branch_format for code that needs to check it later.
821
840
 
822
 
        In the future, we might need different in-memory Branch
823
 
        classes to support downlevel branches.  But not yet.
 
841
        The format parameter is either None or the branch format class
 
842
        used to open this branch.
824
843
        """
825
 
        try:
826
 
            fmt = self.controlfile('branch-format', 'r').read()
827
 
        except NoSuchFile:
828
 
            raise NotBranchError(path=self.base)
829
 
        mutter("got branch format %r", fmt)
830
 
        if fmt == BZR_BRANCH_FORMAT_6:
831
 
            self._branch_format = 6
832
 
        elif fmt == BZR_BRANCH_FORMAT_5:
833
 
            self._branch_format = 5
834
 
        elif fmt == BZR_BRANCH_FORMAT_4:
835
 
            self._branch_format = 4
836
 
 
837
 
        if (not relax_version_check
838
 
            and self._branch_format not in (5, 6)):
839
 
            raise errors.UnsupportedFormatError(
840
 
                           'sorry, branch format %r not supported' % fmt,
841
 
                           ['use a different bzr version',
842
 
                            'or remove the .bzr directory'
843
 
                            ' and "bzr init" again'])
 
844
        if format is None:
 
845
            format = BzrBranchFormat.find_format(self._transport)
 
846
        self._branch_format = format
 
847
        mutter("got branch format %s", self._branch_format)
844
848
 
845
849
    @needs_read_lock
846
850
    def get_root_id(self):
847
851
        """See Branch.get_root_id."""
848
 
        inv = self.get_inventory(self.last_revision())
849
 
        return inv.root.file_id
 
852
        tree = self.repository.revision_tree(self.last_revision())
 
853
        return tree.inventory.root.file_id
 
854
 
 
855
    def lock_write(self):
 
856
        # TODO: test for failed two phase locks. This is known broken.
 
857
        self.control_files.lock_write()
 
858
        self.repository.lock_write()
 
859
 
 
860
    def lock_read(self):
 
861
        # TODO: test for failed two phase locks. This is known broken.
 
862
        self.control_files.lock_read()
 
863
        self.repository.lock_read()
 
864
 
 
865
    def unlock(self):
 
866
        # TODO: test for failed two phase locks. This is known broken.
 
867
        self.repository.unlock()
 
868
        self.control_files.unlock()
 
869
 
 
870
    def peek_lock_mode(self):
 
871
        if self.control_files._lock_count == 0:
 
872
            return None
 
873
        else:
 
874
            return self.control_files._lock_mode
850
875
 
851
876
    @needs_read_lock
852
877
    def print_file(self, file, revision_id):
853
878
        """See Branch.print_file."""
854
 
        tree = self.revision_tree(revision_id)
855
 
        # use inventory as it was in that revision
856
 
        file_id = tree.inventory.path2id(file)
857
 
        if not file_id:
858
 
            try:
859
 
                revno = self.revision_id_to_revno(revision_id)
860
 
            except errors.NoSuchRevision:
861
 
                # TODO: This should not be BzrError,
862
 
                # but NoSuchFile doesn't fit either
863
 
                raise BzrError('%r is not present in revision %s' 
864
 
                                % (file, revision_id))
865
 
            else:
866
 
                raise BzrError('%r is not present in revision %s'
867
 
                                % (file, revno))
868
 
        tree.print_file(file_id)
 
879
        return self.repository.print_file(file, revision_id)
869
880
 
870
881
    @needs_write_lock
871
882
    def append_revision(self, *revision_ids):
879
890
    @needs_write_lock
880
891
    def set_revision_history(self, rev_history):
881
892
        """See Branch.set_revision_history."""
882
 
        old_revision = self.last_revision()
883
 
        new_revision = rev_history[-1]
884
 
        self.put_controlfile('revision-history', '\n'.join(rev_history))
885
 
        try:
886
 
            self.working_tree().set_last_revision(new_revision, old_revision)
887
 
        except NoWorkingTree:
888
 
            mutter('Unable to set_last_revision without a working tree.')
889
 
 
890
 
    def has_revision(self, revision_id):
891
 
        """See Branch.has_revision."""
892
 
        return (revision_id is None
893
 
                or self.revision_store.has_id(revision_id))
894
 
 
895
 
    @needs_read_lock
896
 
    def _get_revision_xml_file(self, revision_id):
897
 
        if not revision_id or not isinstance(revision_id, basestring):
898
 
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
899
 
        try:
900
 
            return self.revision_store.get(revision_id)
901
 
        except (IndexError, KeyError):
902
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
903
 
 
904
 
    def get_revision_xml(self, revision_id):
905
 
        """See Branch.get_revision_xml."""
906
 
        return self._get_revision_xml_file(revision_id).read()
907
 
 
908
 
    def get_revision(self, revision_id):
909
 
        """See Branch.get_revision."""
910
 
        xml_file = self._get_revision_xml_file(revision_id)
911
 
 
912
 
        try:
913
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
914
 
        except SyntaxError, e:
915
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
916
 
                                         [revision_id,
917
 
                                          str(e)])
918
 
            
919
 
        assert r.revision_id == revision_id
920
 
        return r
921
 
 
922
 
    def get_revision_sha1(self, revision_id):
923
 
        """See Branch.get_revision_sha1."""
924
 
        # In the future, revision entries will be signed. At that
925
 
        # point, it is probably best *not* to include the signature
926
 
        # in the revision hash. Because that lets you re-sign
927
 
        # the revision, (add signatures/remove signatures) and still
928
 
        # have all hash pointers stay consistent.
929
 
        # But for now, just hash the contents.
930
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
931
 
 
932
 
    def get_ancestry(self, revision_id):
933
 
        """See Branch.get_ancestry."""
934
 
        if revision_id is None:
935
 
            return [None]
936
 
        w = self._get_inventory_weave()
937
 
        return [None] + map(w.idx_to_name,
938
 
                            w.inclusions([w.lookup(revision_id)]))
939
 
 
940
 
    def _get_inventory_weave(self):
941
 
        return self.control_weaves.get_weave('inventory',
942
 
                                             self.get_transaction())
943
 
 
944
 
    def get_inventory(self, revision_id):
945
 
        """See Branch.get_inventory."""
946
 
        xml = self.get_inventory_xml(revision_id)
947
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
948
 
 
949
 
    def get_inventory_xml(self, revision_id):
950
 
        """See Branch.get_inventory_xml."""
951
 
        try:
952
 
            assert isinstance(revision_id, basestring), type(revision_id)
953
 
            iw = self._get_inventory_weave()
954
 
            return iw.get_text(iw.lookup(revision_id))
955
 
        except IndexError:
956
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
957
 
 
958
 
    def get_inventory_sha1(self, revision_id):
959
 
        """See Branch.get_inventory_sha1."""
960
 
        return self.get_revision(revision_id).inventory_sha1
961
 
 
962
 
    def get_revision_inventory(self, revision_id):
963
 
        """See Branch.get_revision_inventory."""
964
 
        # TODO: Unify this with get_inventory()
965
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
966
 
        # must be the same as its revision, so this is trivial.
967
 
        if revision_id == None:
968
 
            # This does not make sense: if there is no revision,
969
 
            # then it is the current tree inventory surely ?!
970
 
            # and thus get_root_id() is something that looks at the last
971
 
            # commit on the branch, and the get_root_id is an inventory check.
972
 
            raise NotImplementedError
973
 
            # return Inventory(self.get_root_id())
 
893
        self.control_files.put_utf8(
 
894
            'revision-history', '\n'.join(rev_history))
 
895
 
 
896
    def get_revision_delta(self, revno):
 
897
        """Return the delta for one revision.
 
898
 
 
899
        The delta is relative to its mainline predecessor, or the
 
900
        empty tree for revision 1.
 
901
        """
 
902
        assert isinstance(revno, int)
 
903
        rh = self.revision_history()
 
904
        if not (1 <= revno <= len(rh)):
 
905
            raise InvalidRevisionNumber(revno)
 
906
 
 
907
        # revno is 1-based; list is 0-based
 
908
 
 
909
        new_tree = self.repository.revision_tree(rh[revno-1])
 
910
        if revno == 1:
 
911
            old_tree = EmptyTree()
974
912
        else:
975
 
            return self.get_inventory(revision_id)
 
913
            old_tree = self.repository.revision_tree(rh[revno-2])
 
914
        return compare_trees(old_tree, new_tree)
976
915
 
977
916
    @needs_read_lock
978
917
    def revision_history(self):
979
918
        """See Branch.revision_history."""
 
919
        # FIXME are transactions bound to control files ? RBC 20051121
980
920
        transaction = self.get_transaction()
981
921
        history = transaction.map.find_revision_history()
982
922
        if history is not None:
983
923
            mutter("cache hit for revision-history in %s", self)
984
924
            return list(history)
985
925
        history = [l.rstrip('\r\n') for l in
986
 
                self.controlfile('revision-history', 'r').readlines()]
 
926
                self.control_files.get_utf8('revision-history').readlines()]
987
927
        transaction.map.add_revision_history(history)
988
928
        # this call is disabled because revision_history is 
989
929
        # not really an object yet, and the transaction is for objects.
1013
953
        except DivergedBranches, e:
1014
954
            try:
1015
955
                pullable_revs = get_intervening_revisions(self.last_revision(),
1016
 
                                                          stop_revision, self)
 
956
                                                          stop_revision, 
 
957
                                                          self.repository)
1017
958
                assert self.last_revision() not in pullable_revs
1018
959
                return pullable_revs
1019
960
            except bzrlib.errors.NotAncestor:
1022
963
                else:
1023
964
                    raise e
1024
965
        
1025
 
    def revision_tree(self, revision_id):
1026
 
        """See Branch.revision_tree."""
1027
 
        # TODO: refactor this to use an existing revision object
1028
 
        # so we don't need to read it in twice.
1029
 
        if revision_id == None or revision_id == NULL_REVISION:
1030
 
            return EmptyTree()
1031
 
        else:
1032
 
            inv = self.get_revision_inventory(revision_id)
1033
 
            return RevisionTree(self, inv, revision_id)
1034
 
 
1035
966
    def basis_tree(self):
1036
967
        """See Branch.basis_tree."""
1037
968
        try:
1038
969
            revision_id = self.revision_history()[-1]
 
970
            # FIXME: This is an abstraction violation, the basis tree 
 
971
            # here as defined is on the working tree, the method should
 
972
            # be too. The basis tree for a branch can be different than
 
973
            # that for a working tree. RBC 20051207
1039
974
            xml = self.working_tree().read_basis_inventory(revision_id)
1040
975
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1041
 
            return RevisionTree(self, inv, revision_id)
 
976
            return RevisionTree(self.repository, inv, revision_id)
1042
977
        except (IndexError, NoSuchFile, NoWorkingTree), e:
1043
 
            return self.revision_tree(self.last_revision())
 
978
            return self.repository.revision_tree(self.last_revision())
1044
979
 
1045
980
    def working_tree(self):
1046
981
        """See Branch.working_tree."""
1047
982
        from bzrlib.workingtree import WorkingTree
1048
 
        if self._transport.base.find('://') != -1:
 
983
        from bzrlib.transport.local import LocalTransport
 
984
        if (self.base.find('://') != -1 or 
 
985
            not isinstance(self._transport, LocalTransport)):
1049
986
            raise NoWorkingTree(self.base)
1050
987
        return WorkingTree(self.base, branch=self)
1051
988
 
1073
1010
        _locs = ['parent', 'pull', 'x-pull']
1074
1011
        for l in _locs:
1075
1012
            try:
1076
 
                return self.controlfile(l, 'r').read().strip('\n')
 
1013
                return self.control_files.get_utf8(l).read().strip('\n')
1077
1014
            except NoSuchFile:
1078
1015
                pass
1079
1016
        return None
1093
1030
    def set_parent(self, url):
1094
1031
        """See Branch.set_parent."""
1095
1032
        # TODO: Maybe delete old location files?
1096
 
        from bzrlib.atomicfile import AtomicFile
1097
 
        f = AtomicFile(self.controlfilename('parent'))
1098
 
        try:
1099
 
            f.write(url + '\n')
1100
 
            f.commit()
1101
 
        finally:
1102
 
            f.close()
 
1033
        # URLs should never be unicode, even on the local fs,
 
1034
        # FIXUP this and get_parent in a future branch format bump:
 
1035
        # read and rewrite the file, and have the new format code read
 
1036
        # using .get not .get_utf8. RBC 20060125
 
1037
        self.control_files.put_utf8('parent', url + '\n')
1103
1038
 
1104
1039
    def tree_config(self):
1105
1040
        return TreeConfig(self)
1106
1041
 
1107
 
    def sign_revision(self, revision_id, gpg_strategy):
1108
 
        """See Branch.sign_revision."""
1109
 
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1110
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1111
 
 
1112
 
    @needs_write_lock
1113
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1114
 
        """See Branch.store_revision_signature."""
1115
 
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1116
 
                                revision_id, "sig")
 
1042
    def _get_truncated_history(self, revision_id):
 
1043
        history = self.revision_history()
 
1044
        if revision_id is None:
 
1045
            return history
 
1046
        try:
 
1047
            idx = history.index(revision_id)
 
1048
        except ValueError:
 
1049
            raise InvalidRevisionId(revision_id=revision, branch=self)
 
1050
        return history[:idx+1]
 
1051
 
 
1052
    @needs_read_lock
 
1053
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
 
1054
        # prevent leakage
 
1055
        from bzrlib.workingtree import WorkingTree
 
1056
        assert isinstance(to_location, basestring)
 
1057
        if basis_branch is not None:
 
1058
            note("basis_branch is not supported for fast weave copy yet.")
 
1059
 
 
1060
        history = self._get_truncated_history(revision)
 
1061
        if not bzrlib.osutils.lexists(to_location):
 
1062
            os.mkdir(to_location)
 
1063
        branch_to = Branch.initialize(to_location)
 
1064
        mutter("copy branch from %s to %s", self, branch_to)
 
1065
 
 
1066
        self.repository.copy(branch_to.repository)
 
1067
        
 
1068
        # must be done *after* history is copied across
 
1069
        # FIXME duplicate code with base .clone().
 
1070
        # .. would template method be useful here?  RBC 20051207
 
1071
        branch_to.set_parent(self.base)
 
1072
        branch_to.append_revision(*history)
 
1073
        # FIXME: this should be in workingtree.clone
 
1074
        WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
 
1075
        mutter("copied")
 
1076
        return branch_to
 
1077
 
 
1078
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
1079
        print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
 
1080
        if to_branch_type is None:
 
1081
            to_branch_type = BzrBranch
 
1082
 
 
1083
        if to_branch_type == BzrBranch \
 
1084
            and self.repository.weave_store.listable() \
 
1085
            and self.repository.revision_store.listable():
 
1086
            return self._clone_weave(to_location, revision, basis_branch)
 
1087
 
 
1088
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
 
1089
 
 
1090
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
1091
        """Find file_id(s) which are involved in the changes between revisions.
 
1092
 
 
1093
        This determines the set of revisions which are involved, and then
 
1094
        finds all file ids affected by those revisions.
 
1095
        """
 
1096
        # TODO: jam 20060119 This code assumes that w.inclusions will
 
1097
        #       always be correct. But because of the presence of ghosts
 
1098
        #       it is possible to be wrong.
 
1099
        #       One specific example from Robert Collins:
 
1100
        #       Two branches, with revisions ABC, and AD
 
1101
        #       C is a ghost merge of D.
 
1102
        #       Inclusions doesn't recognize D as an ancestor.
 
1103
        #       If D is ever merged in the future, the weave
 
1104
        #       won't be fixed, because AD never saw revision C
 
1105
        #       to cause a conflict which would force a reweave.
 
1106
        w = self.repository.get_inventory_weave()
 
1107
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
1108
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
1109
        included = to_set.difference(from_set)
 
1110
        changed = map(w.idx_to_name, included)
 
1111
        return self._fileid_involved_by_set(changed)
 
1112
 
 
1113
    def fileid_involved(self, last_revid=None):
 
1114
        """Find all file_ids modified in the ancestry of last_revid.
 
1115
 
 
1116
        :param last_revid: If None, last_revision() will be used.
 
1117
        """
 
1118
        w = self.repository.get_inventory_weave()
 
1119
        if not last_revid:
 
1120
            changed = set(w._names)
 
1121
        else:
 
1122
            included = w.inclusions([w.lookup(last_revid)])
 
1123
            changed = map(w.idx_to_name, included)
 
1124
        return self._fileid_involved_by_set(changed)
 
1125
 
 
1126
    def fileid_involved_by_set(self, changes):
 
1127
        """Find all file_ids modified by the set of revisions passed in.
 
1128
 
 
1129
        :param changes: A set() of revision ids
 
1130
        """
 
1131
        # TODO: jam 20060119 This line does *nothing*, remove it.
 
1132
        #       or better yet, change _fileid_involved_by_set so
 
1133
        #       that it takes the inventory weave, rather than
 
1134
        #       pulling it out by itself.
 
1135
        w = self.repository.get_inventory_weave()
 
1136
        return self._fileid_involved_by_set(changes)
 
1137
 
 
1138
    def _fileid_involved_by_set(self, changes):
 
1139
        """Find the set of file-ids affected by the set of revisions.
 
1140
 
 
1141
        :param changes: A set() of revision ids.
 
1142
        :return: A set() of file ids.
 
1143
        
 
1144
        This peaks at the Weave, interpreting each line, looking to
 
1145
        see if it mentions one of the revisions. And if so, includes
 
1146
        the file id mentioned.
 
1147
        This expects both the Weave format, and the serialization
 
1148
        to have a single line per file/directory, and to have
 
1149
        fileid="" and revision="" on that line.
 
1150
        """
 
1151
        assert (isinstance(self._branch_format, BzrBranchFormat5) or
 
1152
                isinstance(self._branch_format, BzrBranchFormat6)), \
 
1153
            "fileid_involved only supported for branches which store inventory as xml"
 
1154
 
 
1155
        w = self.repository.get_inventory_weave()
 
1156
        file_ids = set()
 
1157
        for line in w._weave:
 
1158
 
 
1159
            # it is ugly, but it is due to the weave structure
 
1160
            if not isinstance(line, basestring): continue
 
1161
 
 
1162
            start = line.find('file_id="')+9
 
1163
            if start < 9: continue
 
1164
            end = line.find('"', start)
 
1165
            assert end>= 0
 
1166
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
1167
 
 
1168
            # check if file_id is already present
 
1169
            if file_id in file_ids: continue
 
1170
 
 
1171
            start = line.find('revision="')+10
 
1172
            if start < 10: continue
 
1173
            end = line.find('"', start)
 
1174
            assert end>= 0
 
1175
            revision_id = xml.sax.saxutils.unescape(line[start:end])
 
1176
 
 
1177
            if revision_id in changes:
 
1178
                file_ids.add(file_id)
 
1179
 
 
1180
        return file_ids
 
1181
 
 
1182
 
 
1183
Branch.set_default_initializer(BzrBranch._initialize)
 
1184
 
 
1185
 
 
1186
class BranchTestProviderAdapter(object):
 
1187
    """A tool to generate a suite testing multiple branch formats at once.
 
1188
 
 
1189
    This is done by copying the test once for each transport and injecting
 
1190
    the transport_server, transport_readonly_server, and branch_format
 
1191
    classes into each copy. Each copy is also given a new id() to make it
 
1192
    easy to identify.
 
1193
    """
 
1194
 
 
1195
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1196
        self._transport_server = transport_server
 
1197
        self._transport_readonly_server = transport_readonly_server
 
1198
        self._formats = formats
 
1199
    
 
1200
    def adapt(self, test):
 
1201
        result = TestSuite()
 
1202
        for format in self._formats:
 
1203
            new_test = deepcopy(test)
 
1204
            new_test.transport_server = self._transport_server
 
1205
            new_test.transport_readonly_server = self._transport_readonly_server
 
1206
            new_test.branch_format = format
 
1207
            def make_new_test_id():
 
1208
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1209
                return lambda: new_id
 
1210
            new_test.id = make_new_test_id()
 
1211
            result.addTest(new_test)
 
1212
        return result
1117
1213
 
1118
1214
 
1119
1215
class ScratchBranch(BzrBranch):
1137
1233
        """
1138
1234
        if transport is None:
1139
1235
            transport = bzrlib.transport.local.ScratchTransport()
1140
 
            super(ScratchBranch, self).__init__(transport, init=True)
 
1236
            # local import for scope restriction
 
1237
            from bzrlib.workingtree import WorkingTree
 
1238
            WorkingTree.create_standalone(transport.base)
 
1239
            super(ScratchBranch, self).__init__(transport)
1141
1240
        else:
1142
1241
            super(ScratchBranch, self).__init__(transport)
1143
1242
 
 
1243
        # BzrBranch creates a clone to .bzr and then forgets about the
 
1244
        # original transport. A ScratchTransport() deletes itself and
 
1245
        # everything underneath it when it goes away, so we need to
 
1246
        # grab a local copy to prevent that from happening
 
1247
        self._transport = transport
 
1248
 
1144
1249
        for d in dirs:
1145
1250
            self._transport.mkdir(d)
1146
1251
            
1147
1252
        for f in files:
1148
1253
            self._transport.put(f, 'content of %s' % f)
1149
1254
 
1150
 
 
1151
1255
    def clone(self):
1152
1256
        """
1153
1257
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
1258
        >>> os.listdir(orig.base)
 
1259
        [u'.bzr', u'file1', u'file2']
1154
1260
        >>> clone = orig.clone()
1155
1261
        >>> if os.name != 'nt':
1156
1262
        ...   os.path.samefile(orig.base, clone.base)
1158
1264
        ...   orig.base == clone.base
1159
1265
        ...
1160
1266
        False
1161
 
        >>> os.path.isfile(pathjoin(clone.base, "file1"))
1162
 
        True
 
1267
        >>> os.listdir(clone.base)
 
1268
        [u'.bzr', u'file1', u'file2']
1163
1269
        """
1164
1270
        from shutil import copytree
1165
1271
        from bzrlib.osutils import mkdtemp
1179
1285
    filename = normpath(filename)
1180
1286
    while filename != '':
1181
1287
        head, tail = os.path.split(filename)
1182
 
        ## mutter('check %r for control file' % ((head, tail), ))
 
1288
        ## mutter('check %r for control file' % ((head, tail),))
1183
1289
        if tail == bzrlib.BZRDIR:
1184
1290
            return True
1185
1291
        if filename == head: