/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

Remove last uses of the init= parameter to BzrBranch.

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