/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

Replace copy_tree with transport logic in upgreade.

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