/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

Add a readonly decorator for transports.

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
from copy import deepcopy
 
19
from cStringIO import StringIO
 
20
import errno
 
21
import os
 
22
import shutil
 
23
import sys
 
24
from unittest import TestSuite
 
25
from warnings import warn
 
26
 
 
27
 
 
28
import bzrlib
 
29
from bzrlib.config import TreeConfig
 
30
from bzrlib.delta import compare_trees
 
31
import bzrlib.errors as errors
 
32
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
33
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
34
                           DivergedBranches, LockError, 
 
35
                           UninitializableFormat,
 
36
                           UnlistableStore,
 
37
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
38
                           NoWorkingTree)
 
39
import bzrlib.inventory as inventory
 
40
from bzrlib.inventory import Inventory
 
41
from bzrlib.osutils import (isdir, quotefn,
 
42
                            rename, splitpath, sha_file,
 
43
                            file_kind, abspath, normpath, pathjoin,
 
44
                            safe_unicode,
 
45
                            )
 
46
from bzrlib.textui import show_status
 
47
from bzrlib.trace import mutter, note
 
48
from bzrlib.tree import EmptyTree, RevisionTree
 
49
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
 
50
                             NULL_REVISION)
 
51
from bzrlib.store import copy_all
 
52
from bzrlib.store.text import TextStore
 
53
from bzrlib.store.weave import WeaveStore
 
54
from bzrlib.symbol_versioning import deprecated_nonce, deprecated_passed
 
55
from bzrlib.testament import Testament
 
56
import bzrlib.transactions as transactions
 
57
from bzrlib.transport import Transport, get_transport
 
58
import bzrlib.xml5
 
59
import bzrlib.ui
 
60
 
 
61
 
 
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
63
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
64
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
65
## TODO: Maybe include checks for common corruption of newlines, etc?
 
66
 
 
67
 
 
68
# TODO: Some operations like log might retrieve the same revisions
 
69
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
70
# cache in memory to make this faster.  In general anything can be
 
71
# cached in memory between lock and unlock operations.
 
72
 
 
73
def find_branch(*ignored, **ignored_too):
 
74
    # XXX: leave this here for about one release, then remove it
 
75
    raise NotImplementedError('find_branch() is not supported anymore, '
 
76
                              'please use one of the new branch constructors')
 
77
 
 
78
 
 
79
def needs_read_lock(unbound):
 
80
    """Decorate unbound to take out and release a read lock."""
 
81
    def decorated(self, *args, **kwargs):
 
82
        self.lock_read()
 
83
        try:
 
84
            return unbound(self, *args, **kwargs)
 
85
        finally:
 
86
            self.unlock()
 
87
    return decorated
 
88
 
 
89
 
 
90
def needs_write_lock(unbound):
 
91
    """Decorate unbound to take out and release a write lock."""
 
92
    def decorated(self, *args, **kwargs):
 
93
        self.lock_write()
 
94
        try:
 
95
            return unbound(self, *args, **kwargs)
 
96
        finally:
 
97
            self.unlock()
 
98
    return decorated
 
99
 
 
100
######################################################################
 
101
# branch objects
 
102
 
 
103
class Branch(object):
 
104
    """Branch holding a history of revisions.
 
105
 
 
106
    base
 
107
        Base directory/url of the branch.
 
108
    """
 
109
    # this is really an instance variable - FIXME move it there
 
110
    # - RBC 20060112
 
111
    base = None
 
112
 
 
113
    _default_initializer = None
 
114
    """The default initializer for making new branches."""
 
115
 
 
116
    def __init__(self, *ignored, **ignored_too):
 
117
        raise NotImplementedError('The Branch class is abstract')
 
118
 
 
119
    @staticmethod
 
120
    def open_downlevel(base):
 
121
        """Open a branch which may be of an old format."""
 
122
        return Branch.open(base, _unsupported=True)
 
123
        
 
124
    @staticmethod
 
125
    def open(base, _unsupported=False):
 
126
        """Open an existing branch, rooted at 'base' (url)
 
127
        
 
128
        _unsupported is a private parameter to the Branch class.
 
129
        """
 
130
        t = get_transport(base)
 
131
        mutter("trying to open %r with transport %r", base, t)
 
132
        format = BzrBranchFormat.find_format(t)
 
133
        if not _unsupported and not format.is_supported():
 
134
            # see open_downlevel to open legacy branches.
 
135
            raise errors.UnsupportedFormatError(
 
136
                    'sorry, branch format %s not supported' % format,
 
137
                    ['use a different bzr version',
 
138
                     'or remove the .bzr directory'
 
139
                     ' and "bzr init" again'])
 
140
        return format.open(t)
 
141
 
 
142
    @staticmethod
 
143
    def open_containing(url):
 
144
        """Open an existing branch which contains url.
 
145
        
 
146
        This probes for a branch at url, and searches upwards from there.
 
147
 
 
148
        Basically we keep looking up until we find the control directory or
 
149
        run into the root.  If there isn't one, raises NotBranchError.
 
150
        If there is one, it is returned, along with the unused portion of url.
 
151
        """
 
152
        t = get_transport(url)
 
153
        while True:
 
154
            try:
 
155
                format = BzrBranchFormat.find_format(t)
 
156
                return format.open(t), t.relpath(url)
 
157
            # TODO FIXME, distinguish between formats that cannot be
 
158
            # identified, and a lack of format.
 
159
            except NotBranchError, e:
 
160
                mutter('not a branch in: %r %s', t.base, e)
 
161
            new_t = t.clone('..')
 
162
            if new_t.base == t.base:
 
163
                # reached the root, whatever that may be
 
164
                raise NotBranchError(path=url)
 
165
            t = new_t
 
166
 
 
167
    @staticmethod
 
168
    def initialize(base):
 
169
        """Create a new branch, rooted at 'base' (url)
 
170
        
 
171
        This will call the current default initializer with base
 
172
        as the only parameter.
 
173
        """
 
174
        return Branch._default_initializer(safe_unicode(base))
 
175
 
 
176
    @staticmethod
 
177
    def get_default_initializer():
 
178
        """Return the initializer being used for new branches."""
 
179
        return Branch._default_initializer
 
180
 
 
181
    @staticmethod
 
182
    def set_default_initializer(initializer):
 
183
        """Set the initializer to be used for new branches."""
 
184
        Branch._default_initializer = staticmethod(initializer)
 
185
 
 
186
    def setup_caching(self, cache_root):
 
187
        """Subclasses that care about caching should override this, and set
 
188
        up cached stores located under cache_root.
 
189
        """
 
190
        self.cache_root = cache_root
 
191
 
 
192
    def _get_nick(self):
 
193
        cfg = self.tree_config()
 
194
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
 
195
 
 
196
    def _set_nick(self, nick):
 
197
        cfg = self.tree_config()
 
198
        cfg.set_option(nick, "nickname")
 
199
        assert cfg.get_option("nickname") == nick
 
200
 
 
201
    nick = property(_get_nick, _set_nick)
 
202
        
 
203
    def push_stores(self, branch_to):
 
204
        """Copy the content of this branches store to branch_to."""
 
205
        raise NotImplementedError('push_stores is abstract')
 
206
 
 
207
    def get_transaction(self):
 
208
        """Return the current active transaction.
 
209
 
 
210
        If no transaction is active, this returns a passthrough object
 
211
        for which all data is immediately flushed and no caching happens.
 
212
        """
 
213
        raise NotImplementedError('get_transaction is abstract')
 
214
 
 
215
    def lock_write(self):
 
216
        raise NotImplementedError('lock_write is abstract')
 
217
        
 
218
    def lock_read(self):
 
219
        raise NotImplementedError('lock_read is abstract')
 
220
 
 
221
    def unlock(self):
 
222
        raise NotImplementedError('unlock is abstract')
 
223
 
 
224
    def abspath(self, name):
 
225
        """Return absolute filename for something in the branch
 
226
        
 
227
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
228
        method and not a tree method.
 
229
        """
 
230
        raise NotImplementedError('abspath is abstract')
 
231
 
 
232
    def controlfilename(self, file_or_path):
 
233
        """Return location relative to branch."""
 
234
        raise NotImplementedError('controlfilename is abstract')
 
235
 
 
236
    def controlfile(self, file_or_path, mode='r'):
 
237
        """Open a control file for this branch.
 
238
 
 
239
        There are two classes of file in the control directory: text
 
240
        and binary.  binary files are untranslated byte streams.  Text
 
241
        control files are stored with Unix newlines and in UTF-8, even
 
242
        if the platform or locale defaults are different.
 
243
 
 
244
        Controlfiles should almost never be opened in write mode but
 
245
        rather should be atomically copied and replaced using atomicfile.
 
246
        """
 
247
        raise NotImplementedError('controlfile is abstract')
 
248
 
 
249
    def put_controlfile(self, path, f, encode=True):
 
250
        """Write an entry as a controlfile.
 
251
 
 
252
        :param path: The path to put the file, relative to the .bzr control
 
253
                     directory
 
254
        :param f: A file-like or string object whose contents should be copied.
 
255
        :param encode:  If true, encode the contents as utf-8
 
256
        """
 
257
        raise NotImplementedError('put_controlfile is abstract')
 
258
 
 
259
    def put_controlfiles(self, files, encode=True):
 
260
        """Write several entries as controlfiles.
 
261
 
 
262
        :param files: A list of [(path, file)] pairs, where the path is the directory
 
263
                      underneath the bzr control directory
 
264
        :param encode:  If true, encode the contents as utf-8
 
265
        """
 
266
        raise NotImplementedError('put_controlfiles is abstract')
 
267
 
 
268
    def get_root_id(self):
 
269
        """Return the id of this branches root"""
 
270
        raise NotImplementedError('get_root_id is abstract')
 
271
 
 
272
    def set_root_id(self, file_id):
 
273
        raise NotImplementedError('set_root_id is abstract')
 
274
 
 
275
    def print_file(self, file, revision_id):
 
276
        """Print `file` to stdout."""
 
277
        raise NotImplementedError('print_file is abstract')
 
278
 
 
279
    def append_revision(self, *revision_ids):
 
280
        raise NotImplementedError('append_revision is abstract')
 
281
 
 
282
    def set_revision_history(self, rev_history):
 
283
        raise NotImplementedError('set_revision_history is abstract')
 
284
 
 
285
    def has_revision(self, revision_id):
 
286
        """True if this branch has a copy of the revision.
 
287
 
 
288
        This does not necessarily imply the revision is merge
 
289
        or on the mainline."""
 
290
        raise NotImplementedError('has_revision is abstract')
 
291
 
 
292
    def get_revision_xml(self, revision_id):
 
293
        raise NotImplementedError('get_revision_xml is abstract')
 
294
 
 
295
    def get_revision(self, revision_id):
 
296
        """Return the Revision object for a named revision"""
 
297
        raise NotImplementedError('get_revision is abstract')
 
298
 
 
299
    def get_revision_delta(self, revno):
 
300
        """Return the delta for one revision.
 
301
 
 
302
        The delta is relative to its mainline predecessor, or the
 
303
        empty tree for revision 1.
 
304
        """
 
305
        assert isinstance(revno, int)
 
306
        rh = self.revision_history()
 
307
        if not (1 <= revno <= len(rh)):
 
308
            raise InvalidRevisionNumber(revno)
 
309
 
 
310
        # revno is 1-based; list is 0-based
 
311
 
 
312
        new_tree = self.revision_tree(rh[revno-1])
 
313
        if revno == 1:
 
314
            old_tree = EmptyTree()
 
315
        else:
 
316
            old_tree = self.revision_tree(rh[revno-2])
 
317
 
 
318
        return compare_trees(old_tree, new_tree)
 
319
 
 
320
    def get_revision_sha1(self, revision_id):
 
321
        """Hash the stored value of a revision, and return it."""
 
322
        raise NotImplementedError('get_revision_sha1 is abstract')
 
323
 
 
324
    def get_ancestry(self, revision_id):
 
325
        """Return a list of revision-ids integrated by a revision.
 
326
        
 
327
        This currently returns a list, but the ordering is not guaranteed:
 
328
        treat it as a set.
 
329
        """
 
330
        raise NotImplementedError('get_ancestry is abstract')
 
331
 
 
332
    def get_inventory(self, revision_id):
 
333
        """Get Inventory object by hash."""
 
334
        raise NotImplementedError('get_inventory is abstract')
 
335
 
 
336
    def get_inventory_xml(self, revision_id):
 
337
        """Get inventory XML as a file object."""
 
338
        raise NotImplementedError('get_inventory_xml is abstract')
 
339
 
 
340
    def get_inventory_sha1(self, revision_id):
 
341
        """Return the sha1 hash of the inventory entry."""
 
342
        raise NotImplementedError('get_inventory_sha1 is abstract')
 
343
 
 
344
    def get_revision_inventory(self, revision_id):
 
345
        """Return inventory of a past revision."""
 
346
        raise NotImplementedError('get_revision_inventory is abstract')
 
347
 
 
348
    def revision_history(self):
 
349
        """Return sequence of revision hashes on to this branch."""
 
350
        raise NotImplementedError('revision_history is abstract')
 
351
 
 
352
    def revno(self):
 
353
        """Return current revision number for this branch.
 
354
 
 
355
        That is equivalent to the number of revisions committed to
 
356
        this branch.
 
357
        """
 
358
        return len(self.revision_history())
 
359
 
 
360
    def last_revision(self):
 
361
        """Return last patch hash, or None if no history."""
 
362
        ph = self.revision_history()
 
363
        if ph:
 
364
            return ph[-1]
 
365
        else:
 
366
            return None
 
367
 
 
368
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
369
        """Return a list of new revisions that would perfectly fit.
 
370
        
 
371
        If self and other have not diverged, return a list of the revisions
 
372
        present in other, but missing from self.
 
373
 
 
374
        >>> from bzrlib.commit import commit
 
375
        >>> bzrlib.trace.silent = True
 
376
        >>> br1 = ScratchBranch()
 
377
        >>> br2 = ScratchBranch()
 
378
        >>> br1.missing_revisions(br2)
 
379
        []
 
380
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
381
        >>> br1.missing_revisions(br2)
 
382
        [u'REVISION-ID-1']
 
383
        >>> br2.missing_revisions(br1)
 
384
        []
 
385
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
386
        >>> br1.missing_revisions(br2)
 
387
        []
 
388
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
389
        >>> br1.missing_revisions(br2)
 
390
        [u'REVISION-ID-2A']
 
391
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
392
        >>> br1.missing_revisions(br2)
 
393
        Traceback (most recent call last):
 
394
        DivergedBranches: These branches have diverged.  Try merge.
 
395
        """
 
396
        self_history = self.revision_history()
 
397
        self_len = len(self_history)
 
398
        other_history = other.revision_history()
 
399
        other_len = len(other_history)
 
400
        common_index = min(self_len, other_len) -1
 
401
        if common_index >= 0 and \
 
402
            self_history[common_index] != other_history[common_index]:
 
403
            raise DivergedBranches(self, other)
 
404
 
 
405
        if stop_revision is None:
 
406
            stop_revision = other_len
 
407
        else:
 
408
            assert isinstance(stop_revision, int)
 
409
            if stop_revision > other_len:
 
410
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
411
        return other_history[self_len:stop_revision]
 
412
    
 
413
    def update_revisions(self, other, stop_revision=None):
 
414
        """Pull in new perfect-fit revisions."""
 
415
        raise NotImplementedError('update_revisions is abstract')
 
416
 
 
417
    def pullable_revisions(self, other, stop_revision):
 
418
        raise NotImplementedError('pullable_revisions is abstract')
 
419
        
 
420
    def revision_id_to_revno(self, revision_id):
 
421
        """Given a revision id, return its revno"""
 
422
        if revision_id is None:
 
423
            return 0
 
424
        history = self.revision_history()
 
425
        try:
 
426
            return history.index(revision_id) + 1
 
427
        except ValueError:
 
428
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
429
 
 
430
    def get_rev_id(self, revno, history=None):
 
431
        """Find the revision id of the specified revno."""
 
432
        if revno == 0:
 
433
            return None
 
434
        if history is None:
 
435
            history = self.revision_history()
 
436
        elif revno <= 0 or revno > len(history):
 
437
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
438
        return history[revno - 1]
 
439
 
 
440
    def revision_tree(self, revision_id):
 
441
        """Return Tree for a revision on this branch.
 
442
 
 
443
        `revision_id` may be None for the null revision, in which case
 
444
        an `EmptyTree` is returned."""
 
445
        raise NotImplementedError('revision_tree is abstract')
 
446
 
 
447
    def working_tree(self):
 
448
        """Return a `Tree` for the working copy if this is a local branch."""
 
449
        raise NotImplementedError('working_tree is abstract')
 
450
 
 
451
    def pull(self, source, overwrite=False):
 
452
        raise NotImplementedError('pull is abstract')
 
453
 
 
454
    def basis_tree(self):
 
455
        """Return `Tree` object for last revision.
 
456
 
 
457
        If there are no revisions yet, return an `EmptyTree`.
 
458
        """
 
459
        return self.revision_tree(self.last_revision())
 
460
 
 
461
    def rename_one(self, from_rel, to_rel):
 
462
        """Rename one file.
 
463
 
 
464
        This can change the directory or the filename or both.
 
465
        """
 
466
        raise NotImplementedError('rename_one is abstract')
 
467
 
 
468
    def move(self, from_paths, to_name):
 
469
        """Rename files.
 
470
 
 
471
        to_name must exist as a versioned directory.
 
472
 
 
473
        If to_name exists and is a directory, the files are moved into
 
474
        it, keeping their old names.  If it is a directory, 
 
475
 
 
476
        Note that to_name is only the last component of the new name;
 
477
        this doesn't change the directory.
 
478
 
 
479
        This returns a list of (from_path, to_path) pairs for each
 
480
        entry that is moved.
 
481
        """
 
482
        raise NotImplementedError('move is abstract')
 
483
 
 
484
    def get_parent(self):
 
485
        """Return the parent location of the branch.
 
486
 
 
487
        This is the default location for push/pull/missing.  The usual
 
488
        pattern is that the user can override it by specifying a
 
489
        location.
 
490
        """
 
491
        raise NotImplementedError('get_parent is abstract')
 
492
 
 
493
    def get_push_location(self):
 
494
        """Return the None or the location to push this branch to."""
 
495
        raise NotImplementedError('get_push_location is abstract')
 
496
 
 
497
    def set_push_location(self, location):
 
498
        """Set a new push location for this branch."""
 
499
        raise NotImplementedError('set_push_location is abstract')
 
500
 
 
501
    def set_parent(self, url):
 
502
        raise NotImplementedError('set_parent is abstract')
 
503
 
 
504
    def check_revno(self, revno):
 
505
        """\
 
506
        Check whether a revno corresponds to any revision.
 
507
        Zero (the NULL revision) is considered valid.
 
508
        """
 
509
        if revno != 0:
 
510
            self.check_real_revno(revno)
 
511
            
 
512
    def check_real_revno(self, revno):
 
513
        """\
 
514
        Check whether a revno corresponds to a real revision.
 
515
        Zero (the NULL revision) is considered invalid
 
516
        """
 
517
        if revno < 1 or revno > self.revno():
 
518
            raise InvalidRevisionNumber(revno)
 
519
        
 
520
    def sign_revision(self, revision_id, gpg_strategy):
 
521
        raise NotImplementedError('sign_revision is abstract')
 
522
 
 
523
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
524
        raise NotImplementedError('store_revision_signature is abstract')
 
525
 
 
526
 
 
527
class BzrBranchFormat(object):
 
528
    """An encapsulation of the initialization and open routines for a format.
 
529
 
 
530
    Formats provide three things:
 
531
     * An initialization routine,
 
532
     * a format string,
 
533
     * an open routine.
 
534
 
 
535
    Formats are placed in an dict by their format string for reference 
 
536
    during branch opening. Its not required that these be instances, they
 
537
    can be classes themselves with class methods - it simply depends on 
 
538
    whether state is needed for a given format or not.
 
539
 
 
540
    Once a format is deprecated, just deprecate the initialize and open
 
541
    methods on the format class. Do not deprecate the object, as the 
 
542
    object will be created every time regardless.
 
543
    """
 
544
 
 
545
    _formats = {}
 
546
    """The known formats."""
 
547
 
 
548
    @classmethod
 
549
    def find_format(klass, transport):
 
550
        """Return the format registered for URL."""
 
551
        try:
 
552
            format_string = transport.get(".bzr/branch-format").read()
 
553
            return klass._formats[format_string]
 
554
        except NoSuchFile:
 
555
            raise NotBranchError(path=transport.base)
 
556
        except KeyError:
 
557
            raise errors.UnknownFormatError(format_string)
 
558
 
 
559
    def get_format_string(self):
 
560
        """Return the ASCII format string that identifies this format."""
 
561
        raise NotImplementedError(self.get_format_string)
 
562
 
 
563
    def _find_modes(self, t):
 
564
        """Determine the appropriate modes for files and directories.
 
565
        
 
566
        FIXME: When this merges into, or from storage,
 
567
        this code becomes delgatable to a LockableFiles instance.
 
568
 
 
569
        For now its cribbed and returns (dir_mode, file_mode)
 
570
        """
 
571
        try:
 
572
            st = t.stat('.')
 
573
        except errors.TransportNotPossible:
 
574
            dir_mode = 0755
 
575
            file_mode = 0644
 
576
        else:
 
577
            dir_mode = st.st_mode & 07777
 
578
            # Remove the sticky and execute bits for files
 
579
            file_mode = dir_mode & ~07111
 
580
        if not BzrBranch._set_dir_mode:
 
581
            dir_mode = None
 
582
        if not BzrBranch._set_file_mode:
 
583
            file_mode = None
 
584
        return dir_mode, file_mode
 
585
 
 
586
    def initialize(self, url):
 
587
        """Create a branch of this format at url and return an open branch."""
 
588
        t = get_transport(url)
 
589
        from bzrlib.inventory import Inventory
 
590
        from bzrlib.weavefile import write_weave_v5
 
591
        from bzrlib.weave import Weave
 
592
        
 
593
        # Create an empty inventory
 
594
        sio = StringIO()
 
595
        # if we want per-tree root ids then this is the place to set
 
596
        # them; they're not needed for now and so ommitted for
 
597
        # simplicity.
 
598
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
599
        empty_inv = sio.getvalue()
 
600
        sio = StringIO()
 
601
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
602
        empty_weave = sio.getvalue()
 
603
 
 
604
        # Since we don't have a .bzr directory, inherit the
 
605
        # mode from the root directory
 
606
        dir_mode, file_mode = self._find_modes(t)
 
607
 
 
608
        t.mkdir('.bzr', mode=dir_mode)
 
609
        control = t.clone('.bzr')
 
610
        dirs = ['revision-store', 'weaves']
 
611
        files = [('README', 
 
612
            StringIO("This is a Bazaar-NG control directory.\n"
 
613
            "Do not change any files in this directory.\n")),
 
614
            ('branch-format', StringIO(self.get_format_string())),
 
615
            ('revision-history', StringIO('')),
 
616
            ('branch-name', StringIO('')),
 
617
            ('branch-lock', StringIO('')),
 
618
            ('pending-merges', StringIO('')),
 
619
            ('inventory', StringIO(empty_inv)),
 
620
            ('inventory.weave', StringIO(empty_weave)),
 
621
            ('ancestry.weave', StringIO(empty_weave))
 
622
        ]
 
623
        control.mkdir_multi(dirs, mode=dir_mode)
 
624
        control.put_multi(files, mode=file_mode)
 
625
        mutter('created control directory in ' + t.base)
 
626
        return BzrBranch(t, format=self)
 
627
 
 
628
    def is_supported(self):
 
629
        """Is this format supported?
 
630
 
 
631
        Supported formats can be initialized and opened.
 
632
        Unsupported formats may not support initialization or committing or 
 
633
        some other features depending on the reason for not being supported.
 
634
        """
 
635
        return True
 
636
 
 
637
    def open(self, transport):
 
638
        """Fill out the data in branch for the branch at url."""
 
639
        return BzrBranch(transport, format=self)
 
640
 
 
641
    @classmethod
 
642
    def register_format(klass, format):
 
643
        klass._formats[format.get_format_string()] = format
 
644
 
 
645
    @classmethod
 
646
    def unregister_format(klass, format):
 
647
        assert klass._formats[format.get_format_string()] is format
 
648
        del klass._formats[format.get_format_string()]
 
649
 
 
650
 
 
651
class BzrBranchFormat4(BzrBranchFormat):
 
652
    """Bzr branch format 4.
 
653
 
 
654
    This format has:
 
655
     - flat stores
 
656
     - TextStores for texts, inventories,revisions.
 
657
 
 
658
    This format is deprecated: it indexes texts using a text it which is
 
659
    removed in format 5; write support for this format has been removed.
 
660
    """
 
661
 
 
662
    def get_format_string(self):
 
663
        """See BzrBranchFormat.get_format_string()."""
 
664
        return BZR_BRANCH_FORMAT_4
 
665
 
 
666
    def initialize(self, url):
 
667
        """Format 4 branches cannot be created."""
 
668
        raise UninitializableFormat(self)
 
669
 
 
670
    def is_supported(self):
 
671
        """Format 4 is not supported.
 
672
 
 
673
        It is not supported because the model changed from 4 to 5 and the
 
674
        conversion logic is expensive - so doing it on the fly was not 
 
675
        feasible.
 
676
        """
 
677
        return False
 
678
 
 
679
 
 
680
class BzrBranchFormat5(BzrBranchFormat):
 
681
    """Bzr branch format 5.
 
682
 
 
683
    This format has:
 
684
     - weaves for file texts and inventory
 
685
     - flat stores
 
686
     - TextStores for revisions and signatures.
 
687
    """
 
688
 
 
689
    def get_format_string(self):
 
690
        """See BzrBranchFormat.get_format_string()."""
 
691
        return BZR_BRANCH_FORMAT_5
 
692
 
 
693
 
 
694
class BzrBranchFormat6(BzrBranchFormat):
 
695
    """Bzr branch format 6.
 
696
 
 
697
    This format has:
 
698
     - weaves for file texts and inventory
 
699
     - hash subdirectory based stores.
 
700
     - TextStores for revisions and signatures.
 
701
    """
 
702
 
 
703
    def get_format_string(self):
 
704
        """See BzrBranchFormat.get_format_string()."""
 
705
        return BZR_BRANCH_FORMAT_6
 
706
 
 
707
 
 
708
BzrBranchFormat.register_format(BzrBranchFormat4())
 
709
BzrBranchFormat.register_format(BzrBranchFormat5())
 
710
BzrBranchFormat.register_format(BzrBranchFormat6())
 
711
 
 
712
 
 
713
class BzrBranch(Branch):
 
714
    """A branch stored in the actual filesystem.
 
715
 
 
716
    Note that it's "local" in the context of the filesystem; it doesn't
 
717
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
718
    it's writable, and can be accessed via the normal filesystem API.
 
719
 
 
720
    _lock_mode
 
721
        None, or 'r' or 'w'
 
722
 
 
723
    _lock_count
 
724
        If _lock_mode is true, a positive count of the number of times the
 
725
        lock has been taken.
 
726
 
 
727
    _lock
 
728
        Lock object from bzrlib.lock.
 
729
    """
 
730
    # We actually expect this class to be somewhat short-lived; part of its
 
731
    # purpose is to try to isolate what bits of the branch logic are tied to
 
732
    # filesystem access, so that in a later step, we can extricate them to
 
733
    # a separarte ("storage") class.
 
734
    _lock_mode = None
 
735
    _lock_count = None
 
736
    _lock = None
 
737
    _inventory_weave = None
 
738
    # If set to False (by a plugin, etc) BzrBranch will not set the
 
739
    # mode on created files or directories
 
740
    _set_file_mode = True
 
741
    _set_dir_mode = True
 
742
    
 
743
    # Map some sort of prefix into a namespace
 
744
    # stuff like "revno:10", "revid:", etc.
 
745
    # This should match a prefix with a function which accepts
 
746
    REVISION_NAMESPACES = {}
 
747
 
 
748
    def push_stores(self, branch_to):
 
749
        """See Branch.push_stores."""
 
750
        if (not isinstance(self._branch_format, BzrBranchFormat4) or
 
751
            self._branch_format != branch_to._branch_format):
 
752
            from bzrlib.fetch import greedy_fetch
 
753
            mutter("Using fetch logic to push between %s(%s) and %s(%s)",
 
754
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
755
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
756
                         revision=self.last_revision())
 
757
            return
 
758
 
 
759
        # format 4 to format 4 logic only.
 
760
        store_pairs = ((self.text_store,      branch_to.text_store),
 
761
                       (self.inventory_store, branch_to.inventory_store),
 
762
                       (self.revision_store,  branch_to.revision_store))
 
763
        try:
 
764
            for from_store, to_store in store_pairs: 
 
765
                copy_all(from_store, to_store)
 
766
        except UnlistableStore:
 
767
            raise UnlistableBranch(from_store)
 
768
 
 
769
    def __init__(self, transport, init=deprecated_nonce,
 
770
                 relax_version_check=deprecated_nonce, format=None):
 
771
        """Create new branch object at a particular location.
 
772
 
 
773
        transport -- A Transport object, defining how to access files.
 
774
        
 
775
        init -- If True, create new control files in a previously
 
776
             unversioned directory.  If False, the branch must already
 
777
             be versioned.
 
778
 
 
779
        relax_version_check -- If true, the usual check for the branch
 
780
            version is not applied.  This is intended only for
 
781
            upgrade/recovery type use; it's not guaranteed that
 
782
            all operations will work on old format branches.
 
783
 
 
784
        In the test suite, creation of new trees is tested using the
 
785
        `ScratchBranch` class.
 
786
        """
 
787
        assert isinstance(transport, Transport), \
 
788
            "%r is not a Transport" % transport
 
789
        self._transport = transport
 
790
        if deprecated_passed(init):
 
791
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
792
                 "deprecated as of bzr 0.8. Please use Branch.initialize().",
 
793
                 DeprecationWarning)
 
794
            if init:
 
795
                # this is slower than before deprecation, oh well never mind.
 
796
                # -> its deprecated.
 
797
                self._initialize(transport.base)
 
798
        self._find_modes()
 
799
        self._check_format(format)
 
800
        if deprecated_passed(relax_version_check):
 
801
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
802
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
803
                 "Please use Branch.open_downlevel, or a BzrBranchFormat's "
 
804
                 "open() method.", DeprecationWarning)
 
805
            if (not relax_version_check
 
806
                and not self._branch_format.is_supported()):
 
807
                raise errors.UnsupportedFormatError(
 
808
                        'sorry, branch format %r not supported' % fmt,
 
809
                        ['use a different bzr version',
 
810
                         'or remove the .bzr directory'
 
811
                         ' and "bzr init" again'])
 
812
 
 
813
        def get_store(name, compressed=True, prefixed=False):
 
814
            relpath = self._rel_controlfilename(safe_unicode(name))
 
815
            store = TextStore(self._transport.clone(relpath),
 
816
                              dir_mode=self._dir_mode,
 
817
                              file_mode=self._file_mode,
 
818
                              prefixed=prefixed,
 
819
                              compressed=compressed)
 
820
            return store
 
821
 
 
822
        def get_weave(name, prefixed=False):
 
823
            relpath = self._rel_controlfilename(unicode(name))
 
824
            ws = WeaveStore(self._transport.clone(relpath),
 
825
                            prefixed=prefixed,
 
826
                            dir_mode=self._dir_mode,
 
827
                            file_mode=self._file_mode)
 
828
            if self._transport.should_cache():
 
829
                ws.enable_cache = True
 
830
            return ws
 
831
 
 
832
        if isinstance(self._branch_format, BzrBranchFormat4):
 
833
            self.inventory_store = get_store('inventory-store')
 
834
            self.text_store = get_store('text-store')
 
835
            self.revision_store = get_store('revision-store')
 
836
        elif isinstance(self._branch_format, BzrBranchFormat5):
 
837
            self.control_weaves = get_weave(u'')
 
838
            self.weave_store = get_weave(u'weaves')
 
839
            self.revision_store = get_store(u'revision-store', compressed=False)
 
840
        elif isinstance(self._branch_format, BzrBranchFormat6):
 
841
            self.control_weaves = get_weave(u'')
 
842
            self.weave_store = get_weave(u'weaves', prefixed=True)
 
843
            self.revision_store = get_store(u'revision-store', compressed=False,
 
844
                                            prefixed=True)
 
845
        self.revision_store.register_suffix('sig')
 
846
        self._transaction = None
 
847
 
 
848
    @staticmethod
 
849
    def _initialize(base):
 
850
        """Create a bzr branch in the latest format."""
 
851
        return BzrBranchFormat6().initialize(base)
 
852
 
 
853
    def __str__(self):
 
854
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
 
855
 
 
856
    __repr__ = __str__
 
857
 
 
858
    def __del__(self):
 
859
        if self._lock_mode or self._lock:
 
860
            # XXX: This should show something every time, and be suitable for
 
861
            # headless operation and embedding
 
862
            warn("branch %r was not explicitly unlocked" % self)
 
863
            self._lock.unlock()
 
864
 
 
865
        # TODO: It might be best to do this somewhere else,
 
866
        # but it is nice for a Branch object to automatically
 
867
        # cache it's information.
 
868
        # Alternatively, we could have the Transport objects cache requests
 
869
        # See the earlier discussion about how major objects (like Branch)
 
870
        # should never expect their __del__ function to run.
 
871
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
872
            try:
 
873
                shutil.rmtree(self.cache_root)
 
874
            except:
 
875
                pass
 
876
            self.cache_root = None
 
877
 
 
878
    def _get_base(self):
 
879
        if self._transport:
 
880
            return self._transport.base
 
881
        return None
 
882
 
 
883
    base = property(_get_base, doc="The URL for the root of this branch.")
 
884
 
 
885
    def _finish_transaction(self):
 
886
        """Exit the current transaction."""
 
887
        if self._transaction is None:
 
888
            raise errors.LockError('Branch %s is not in a transaction' %
 
889
                                   self)
 
890
        transaction = self._transaction
 
891
        self._transaction = None
 
892
        transaction.finish()
 
893
 
 
894
    def get_transaction(self):
 
895
        """See Branch.get_transaction."""
 
896
        if self._transaction is None:
 
897
            return transactions.PassThroughTransaction()
 
898
        else:
 
899
            return self._transaction
 
900
 
 
901
    def _set_transaction(self, new_transaction):
 
902
        """Set a new active transaction."""
 
903
        if self._transaction is not None:
 
904
            raise errors.LockError('Branch %s is in a transaction already.' %
 
905
                                   self)
 
906
        self._transaction = new_transaction
 
907
 
 
908
    def lock_write(self):
 
909
        #mutter("lock write: %s (%s)", self, self._lock_count)
 
910
        # TODO: Upgrade locking to support using a Transport,
 
911
        # and potentially a remote locking protocol
 
912
        if self._lock_mode:
 
913
            if self._lock_mode != 'w':
 
914
                raise LockError("can't upgrade to a write lock from %r" %
 
915
                                self._lock_mode)
 
916
            self._lock_count += 1
 
917
        else:
 
918
            self._lock = self._transport.lock_write(
 
919
                    self._rel_controlfilename('branch-lock'))
 
920
            self._lock_mode = 'w'
 
921
            self._lock_count = 1
 
922
            self._set_transaction(transactions.PassThroughTransaction())
 
923
 
 
924
    def lock_read(self):
 
925
        #mutter("lock read: %s (%s)", self, self._lock_count)
 
926
        if self._lock_mode:
 
927
            assert self._lock_mode in ('r', 'w'), \
 
928
                   "invalid lock mode %r" % self._lock_mode
 
929
            self._lock_count += 1
 
930
        else:
 
931
            self._lock = self._transport.lock_read(
 
932
                    self._rel_controlfilename('branch-lock'))
 
933
            self._lock_mode = 'r'
 
934
            self._lock_count = 1
 
935
            self._set_transaction(transactions.ReadOnlyTransaction())
 
936
            # 5K may be excessive, but hey, its a knob.
 
937
            self.get_transaction().set_cache_size(5000)
 
938
                        
 
939
    def unlock(self):
 
940
        #mutter("unlock: %s (%s)", self, self._lock_count)
 
941
        if not self._lock_mode:
 
942
            raise LockError('branch %r is not locked' % (self))
 
943
 
 
944
        if self._lock_count > 1:
 
945
            self._lock_count -= 1
 
946
        else:
 
947
            self._finish_transaction()
 
948
            self._lock.unlock()
 
949
            self._lock = None
 
950
            self._lock_mode = self._lock_count = None
 
951
 
 
952
    def abspath(self, name):
 
953
        """See Branch.abspath."""
 
954
        return self._transport.abspath(name)
 
955
 
 
956
    def _rel_controlfilename(self, file_or_path):
 
957
        if not isinstance(file_or_path, basestring):
 
958
            file_or_path = u'/'.join(file_or_path)
 
959
        if file_or_path == '':
 
960
            return bzrlib.BZRDIR
 
961
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
 
962
 
 
963
    def controlfilename(self, file_or_path):
 
964
        """See Branch.controlfilename."""
 
965
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
 
966
 
 
967
    def controlfile(self, file_or_path, mode='r'):
 
968
        """See Branch.controlfile."""
 
969
        import codecs
 
970
 
 
971
        relpath = self._rel_controlfilename(file_or_path)
 
972
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
973
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
974
        if mode == 'rb': 
 
975
            return self._transport.get(relpath)
 
976
        elif mode == 'wb':
 
977
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
 
978
        elif mode == 'r':
 
979
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
980
            # an error, or at least reported, if there's incorrectly-encoded
 
981
            # data inside a file.
 
982
            # <https://launchpad.net/products/bzr/+bug/3823>
 
983
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
984
        elif mode == 'w':
 
985
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
 
986
        else:
 
987
            raise BzrError("invalid controlfile mode %r" % mode)
 
988
 
 
989
    def put_controlfile(self, path, f, encode=True):
 
990
        """See Branch.put_controlfile."""
 
991
        self.put_controlfiles([(path, f)], encode=encode)
 
992
 
 
993
    def put_controlfiles(self, files, encode=True):
 
994
        """See Branch.put_controlfiles."""
 
995
        import codecs
 
996
        ctrl_files = []
 
997
        for path, f in files:
 
998
            if encode:
 
999
                if isinstance(f, basestring):
 
1000
                    f = f.encode('utf-8', 'replace')
 
1001
                else:
 
1002
                    f = codecs.getwriter('utf-8')(f, errors='replace')
 
1003
            path = self._rel_controlfilename(path)
 
1004
            ctrl_files.append((path, f))
 
1005
        self._transport.put_multi(ctrl_files, mode=self._file_mode)
 
1006
 
 
1007
    def _find_modes(self, path=None):
 
1008
        """Determine the appropriate modes for files and directories."""
 
1009
        try:
 
1010
            if path is None:
 
1011
                path = self._rel_controlfilename('')
 
1012
            st = self._transport.stat(path)
 
1013
        except errors.TransportNotPossible:
 
1014
            self._dir_mode = 0755
 
1015
            self._file_mode = 0644
 
1016
        else:
 
1017
            self._dir_mode = st.st_mode & 07777
 
1018
            # Remove the sticky and execute bits for files
 
1019
            self._file_mode = self._dir_mode & ~07111
 
1020
        if not self._set_dir_mode:
 
1021
            self._dir_mode = None
 
1022
        if not self._set_file_mode:
 
1023
            self._file_mode = None
 
1024
 
 
1025
    def _check_format(self, format):
 
1026
        """Identify the branch format if needed.
 
1027
 
 
1028
        The format is stored as a reference to the format object in
 
1029
        self._branch_format for code that needs to check it later.
 
1030
 
 
1031
        The format parameter is either None or the branch format class
 
1032
        used to open this branch.
 
1033
        """
 
1034
        if format is None:
 
1035
            format = BzrBranchFormat.find_format(self._transport)
 
1036
        self._branch_format = format
 
1037
        mutter("got branch format %s", self._branch_format)
 
1038
 
 
1039
    @needs_read_lock
 
1040
    def get_root_id(self):
 
1041
        """See Branch.get_root_id."""
 
1042
        inv = self.get_inventory(self.last_revision())
 
1043
        return inv.root.file_id
 
1044
 
 
1045
    @needs_read_lock
 
1046
    def print_file(self, file, revision_id):
 
1047
        """See Branch.print_file."""
 
1048
        tree = self.revision_tree(revision_id)
 
1049
        # use inventory as it was in that revision
 
1050
        file_id = tree.inventory.path2id(file)
 
1051
        if not file_id:
 
1052
            try:
 
1053
                revno = self.revision_id_to_revno(revision_id)
 
1054
            except errors.NoSuchRevision:
 
1055
                # TODO: This should not be BzrError,
 
1056
                # but NoSuchFile doesn't fit either
 
1057
                raise BzrError('%r is not present in revision %s' 
 
1058
                                % (file, revision_id))
 
1059
            else:
 
1060
                raise BzrError('%r is not present in revision %s'
 
1061
                                % (file, revno))
 
1062
        tree.print_file(file_id)
 
1063
 
 
1064
    @needs_write_lock
 
1065
    def append_revision(self, *revision_ids):
 
1066
        """See Branch.append_revision."""
 
1067
        for revision_id in revision_ids:
 
1068
            mutter("add {%s} to revision-history" % revision_id)
 
1069
        rev_history = self.revision_history()
 
1070
        rev_history.extend(revision_ids)
 
1071
        self.set_revision_history(rev_history)
 
1072
 
 
1073
    @needs_write_lock
 
1074
    def set_revision_history(self, rev_history):
 
1075
        """See Branch.set_revision_history."""
 
1076
        old_revision = self.last_revision()
 
1077
        new_revision = rev_history[-1]
 
1078
        self.put_controlfile('revision-history', '\n'.join(rev_history))
 
1079
        try:
 
1080
            self.working_tree().set_last_revision(new_revision, old_revision)
 
1081
        except NoWorkingTree:
 
1082
            mutter('Unable to set_last_revision without a working tree.')
 
1083
 
 
1084
    def has_revision(self, revision_id):
 
1085
        """See Branch.has_revision."""
 
1086
        return (revision_id is None
 
1087
                or self.revision_store.has_id(revision_id))
 
1088
 
 
1089
    @needs_read_lock
 
1090
    def _get_revision_xml_file(self, revision_id):
 
1091
        if not revision_id or not isinstance(revision_id, basestring):
 
1092
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
1093
        try:
 
1094
            return self.revision_store.get(revision_id)
 
1095
        except (IndexError, KeyError):
 
1096
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
1097
 
 
1098
    def get_revision_xml(self, revision_id):
 
1099
        """See Branch.get_revision_xml."""
 
1100
        return self._get_revision_xml_file(revision_id).read()
 
1101
 
 
1102
    def get_revision(self, revision_id):
 
1103
        """See Branch.get_revision."""
 
1104
        xml_file = self._get_revision_xml_file(revision_id)
 
1105
 
 
1106
        try:
 
1107
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
1108
        except SyntaxError, e:
 
1109
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
1110
                                         [revision_id,
 
1111
                                          str(e)])
 
1112
            
 
1113
        assert r.revision_id == revision_id
 
1114
        return r
 
1115
 
 
1116
    def get_revision_sha1(self, revision_id):
 
1117
        """See Branch.get_revision_sha1."""
 
1118
        # In the future, revision entries will be signed. At that
 
1119
        # point, it is probably best *not* to include the signature
 
1120
        # in the revision hash. Because that lets you re-sign
 
1121
        # the revision, (add signatures/remove signatures) and still
 
1122
        # have all hash pointers stay consistent.
 
1123
        # But for now, just hash the contents.
 
1124
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
1125
 
 
1126
    def get_ancestry(self, revision_id):
 
1127
        """See Branch.get_ancestry."""
 
1128
        if revision_id is None:
 
1129
            return [None]
 
1130
        w = self._get_inventory_weave()
 
1131
        return [None] + map(w.idx_to_name,
 
1132
                            w.inclusions([w.lookup(revision_id)]))
 
1133
 
 
1134
    def _get_inventory_weave(self):
 
1135
        return self.control_weaves.get_weave('inventory',
 
1136
                                             self.get_transaction())
 
1137
 
 
1138
    def get_inventory(self, revision_id):
 
1139
        """See Branch.get_inventory."""
 
1140
        xml = self.get_inventory_xml(revision_id)
 
1141
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
1142
 
 
1143
    def get_inventory_xml(self, revision_id):
 
1144
        """See Branch.get_inventory_xml."""
 
1145
        try:
 
1146
            assert isinstance(revision_id, basestring), type(revision_id)
 
1147
            iw = self._get_inventory_weave()
 
1148
            return iw.get_text(iw.lookup(revision_id))
 
1149
        except IndexError:
 
1150
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
1151
 
 
1152
    def get_inventory_sha1(self, revision_id):
 
1153
        """See Branch.get_inventory_sha1."""
 
1154
        return self.get_revision(revision_id).inventory_sha1
 
1155
 
 
1156
    def get_revision_inventory(self, revision_id):
 
1157
        """See Branch.get_revision_inventory."""
 
1158
        # TODO: Unify this with get_inventory()
 
1159
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
1160
        # must be the same as its revision, so this is trivial.
 
1161
        if revision_id == None:
 
1162
            # This does not make sense: if there is no revision,
 
1163
            # then it is the current tree inventory surely ?!
 
1164
            # and thus get_root_id() is something that looks at the last
 
1165
            # commit on the branch, and the get_root_id is an inventory check.
 
1166
            raise NotImplementedError
 
1167
            # return Inventory(self.get_root_id())
 
1168
        else:
 
1169
            return self.get_inventory(revision_id)
 
1170
 
 
1171
    @needs_read_lock
 
1172
    def revision_history(self):
 
1173
        """See Branch.revision_history."""
 
1174
        transaction = self.get_transaction()
 
1175
        history = transaction.map.find_revision_history()
 
1176
        if history is not None:
 
1177
            mutter("cache hit for revision-history in %s", self)
 
1178
            return list(history)
 
1179
        history = [l.rstrip('\r\n') for l in
 
1180
                self.controlfile('revision-history', 'r').readlines()]
 
1181
        transaction.map.add_revision_history(history)
 
1182
        # this call is disabled because revision_history is 
 
1183
        # not really an object yet, and the transaction is for objects.
 
1184
        # transaction.register_clean(history, precious=True)
 
1185
        return list(history)
 
1186
 
 
1187
    def update_revisions(self, other, stop_revision=None):
 
1188
        """See Branch.update_revisions."""
 
1189
        from bzrlib.fetch import greedy_fetch
 
1190
        if stop_revision is None:
 
1191
            stop_revision = other.last_revision()
 
1192
        ### Should this be checking is_ancestor instead of revision_history?
 
1193
        if (stop_revision is not None and 
 
1194
            stop_revision in self.revision_history()):
 
1195
            return
 
1196
        greedy_fetch(to_branch=self, from_branch=other,
 
1197
                     revision=stop_revision)
 
1198
        pullable_revs = self.pullable_revisions(other, stop_revision)
 
1199
        if len(pullable_revs) > 0:
 
1200
            self.append_revision(*pullable_revs)
 
1201
 
 
1202
    def pullable_revisions(self, other, stop_revision):
 
1203
        """See Branch.pullable_revisions."""
 
1204
        other_revno = other.revision_id_to_revno(stop_revision)
 
1205
        try:
 
1206
            return self.missing_revisions(other, other_revno)
 
1207
        except DivergedBranches, e:
 
1208
            try:
 
1209
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
1210
                                                          stop_revision, self)
 
1211
                assert self.last_revision() not in pullable_revs
 
1212
                return pullable_revs
 
1213
            except bzrlib.errors.NotAncestor:
 
1214
                if is_ancestor(self.last_revision(), stop_revision, self):
 
1215
                    return []
 
1216
                else:
 
1217
                    raise e
 
1218
        
 
1219
    def revision_tree(self, revision_id):
 
1220
        """See Branch.revision_tree."""
 
1221
        # TODO: refactor this to use an existing revision object
 
1222
        # so we don't need to read it in twice.
 
1223
        if revision_id == None or revision_id == NULL_REVISION:
 
1224
            return EmptyTree()
 
1225
        else:
 
1226
            inv = self.get_revision_inventory(revision_id)
 
1227
            return RevisionTree(self, inv, revision_id)
 
1228
 
 
1229
    def basis_tree(self):
 
1230
        """See Branch.basis_tree."""
 
1231
        try:
 
1232
            revision_id = self.revision_history()[-1]
 
1233
            xml = self.working_tree().read_basis_inventory(revision_id)
 
1234
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
1235
            return RevisionTree(self, inv, revision_id)
 
1236
        except (IndexError, NoSuchFile, NoWorkingTree), e:
 
1237
            return self.revision_tree(self.last_revision())
 
1238
 
 
1239
    def working_tree(self):
 
1240
        """See Branch.working_tree."""
 
1241
        from bzrlib.workingtree import WorkingTree
 
1242
        if self._transport.base.find('://') != -1:
 
1243
            raise NoWorkingTree(self.base)
 
1244
        return WorkingTree(self.base, branch=self)
 
1245
 
 
1246
    @needs_write_lock
 
1247
    def pull(self, source, overwrite=False):
 
1248
        """See Branch.pull."""
 
1249
        source.lock_read()
 
1250
        try:
 
1251
            old_count = len(self.revision_history())
 
1252
            try:
 
1253
                self.update_revisions(source)
 
1254
            except DivergedBranches:
 
1255
                if not overwrite:
 
1256
                    raise
 
1257
            if overwrite:
 
1258
                self.set_revision_history(source.revision_history())
 
1259
            new_count = len(self.revision_history())
 
1260
            return new_count - old_count
 
1261
        finally:
 
1262
            source.unlock()
 
1263
 
 
1264
    def get_parent(self):
 
1265
        """See Branch.get_parent."""
 
1266
        import errno
 
1267
        _locs = ['parent', 'pull', 'x-pull']
 
1268
        for l in _locs:
 
1269
            try:
 
1270
                return self.controlfile(l, 'r').read().strip('\n')
 
1271
            except NoSuchFile:
 
1272
                pass
 
1273
        return None
 
1274
 
 
1275
    def get_push_location(self):
 
1276
        """See Branch.get_push_location."""
 
1277
        config = bzrlib.config.BranchConfig(self)
 
1278
        push_loc = config.get_user_option('push_location')
 
1279
        return push_loc
 
1280
 
 
1281
    def set_push_location(self, location):
 
1282
        """See Branch.set_push_location."""
 
1283
        config = bzrlib.config.LocationConfig(self.base)
 
1284
        config.set_user_option('push_location', location)
 
1285
 
 
1286
    @needs_write_lock
 
1287
    def set_parent(self, url):
 
1288
        """See Branch.set_parent."""
 
1289
        # TODO: Maybe delete old location files?
 
1290
        from bzrlib.atomicfile import AtomicFile
 
1291
        f = AtomicFile(self.controlfilename('parent'))
 
1292
        try:
 
1293
            f.write(url + '\n')
 
1294
            f.commit()
 
1295
        finally:
 
1296
            f.close()
 
1297
 
 
1298
    def tree_config(self):
 
1299
        return TreeConfig(self)
 
1300
 
 
1301
    def sign_revision(self, revision_id, gpg_strategy):
 
1302
        """See Branch.sign_revision."""
 
1303
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
1304
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1305
 
 
1306
    @needs_write_lock
 
1307
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1308
        """See Branch.store_revision_signature."""
 
1309
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
1310
                                revision_id, "sig")
 
1311
 
 
1312
 
 
1313
Branch.set_default_initializer(BzrBranch._initialize)
 
1314
 
 
1315
 
 
1316
class BranchTestProviderAdapter(object):
 
1317
    """A tool to generate a suite testing multiple branch formats at once.
 
1318
 
 
1319
    This is done by copying the test once for each transport and injecting
 
1320
    the transport_server, transport_readonly_server, and branch_format
 
1321
    classes into each copy. Each copy is also given a new id() to make it
 
1322
    easy to identify.
 
1323
    """
 
1324
 
 
1325
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1326
        self._transport_server = transport_server
 
1327
        self._transport_readonly_server = transport_readonly_server
 
1328
        self._formats = formats
 
1329
    
 
1330
    def adapt(self, test):
 
1331
        result = TestSuite()
 
1332
        for format in self._formats:
 
1333
            new_test = deepcopy(test)
 
1334
            new_test.transport_server = self._transport_server
 
1335
            new_test.transport_readonly_server = self._transport_readonly_server
 
1336
            new_test.branch_format = format
 
1337
            def make_new_test_id():
 
1338
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1339
                return lambda: new_id
 
1340
            new_test.id = make_new_test_id()
 
1341
            result.addTest(new_test)
 
1342
        return result
 
1343
 
 
1344
 
 
1345
class ScratchBranch(BzrBranch):
 
1346
    """Special test class: a branch that cleans up after itself.
 
1347
 
 
1348
    >>> b = ScratchBranch()
 
1349
    >>> isdir(b.base)
 
1350
    True
 
1351
    >>> bd = b.base
 
1352
    >>> b._transport.__del__()
 
1353
    >>> isdir(bd)
 
1354
    False
 
1355
    """
 
1356
 
 
1357
    def __init__(self, files=[], dirs=[], transport=None):
 
1358
        """Make a test branch.
 
1359
 
 
1360
        This creates a temporary directory and runs init-tree in it.
 
1361
 
 
1362
        If any files are listed, they are created in the working copy.
 
1363
        """
 
1364
        if transport is None:
 
1365
            transport = bzrlib.transport.local.ScratchTransport()
 
1366
            Branch.initialize(transport.base)
 
1367
            super(ScratchBranch, self).__init__(transport)
 
1368
        else:
 
1369
            super(ScratchBranch, self).__init__(transport)
 
1370
 
 
1371
        for d in dirs:
 
1372
            self._transport.mkdir(d)
 
1373
            
 
1374
        for f in files:
 
1375
            self._transport.put(f, 'content of %s' % f)
 
1376
 
 
1377
 
 
1378
    def clone(self):
 
1379
        """
 
1380
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
1381
        >>> clone = orig.clone()
 
1382
        >>> if os.name != 'nt':
 
1383
        ...   os.path.samefile(orig.base, clone.base)
 
1384
        ... else:
 
1385
        ...   orig.base == clone.base
 
1386
        ...
 
1387
        False
 
1388
        >>> os.path.isfile(pathjoin(clone.base, "file1"))
 
1389
        True
 
1390
        """
 
1391
        from shutil import copytree
 
1392
        from bzrlib.osutils import mkdtemp
 
1393
        base = mkdtemp()
 
1394
        os.rmdir(base)
 
1395
        copytree(self.base, base, symlinks=True)
 
1396
        return ScratchBranch(
 
1397
            transport=bzrlib.transport.local.ScratchTransport(base))
 
1398
    
 
1399
 
 
1400
######################################################################
 
1401
# predicates
 
1402
 
 
1403
 
 
1404
def is_control_file(filename):
 
1405
    ## FIXME: better check
 
1406
    filename = normpath(filename)
 
1407
    while filename != '':
 
1408
        head, tail = os.path.split(filename)
 
1409
        ## mutter('check %r for control file' % ((head, tail), ))
 
1410
        if tail == bzrlib.BZRDIR:
 
1411
            return True
 
1412
        if filename == head:
 
1413
            break
 
1414
        filename = head
 
1415
    return False