~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-18 18:17:19 UTC
  • mfrom: (1711.2.133 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060818181719-90004a4648d8537a
(cfbolz,hpk,robertc,jam) Add SFTP benchmark tests, and tests across a delayed socket

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
import sys
19
 
import os
20
 
import errno
 
18
from copy import deepcopy
 
19
from cStringIO import StringIO
 
20
from unittest import TestSuite
21
21
from warnings import warn
22
 
from cStringIO import StringIO
23
 
 
24
22
 
25
23
import bzrlib
26
 
from bzrlib.inventory import InventoryEntry
27
 
import bzrlib.inventory as inventory
 
24
from bzrlib import (
 
25
        bzrdir,
 
26
        cache_utf8,
 
27
        errors,
 
28
        lockdir,
 
29
        osutils,
 
30
        revision,
 
31
        transport,
 
32
        tree,
 
33
        ui,
 
34
        urlutils,
 
35
        )
 
36
from bzrlib.config import TreeConfig
 
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
38
import bzrlib.errors as errors
 
39
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
 
40
                           HistoryMissing, InvalidRevisionId, 
 
41
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
42
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
 
43
                           NotBranchError, UninitializableFormat, 
 
44
                           UnlistableStore, UnlistableBranch, 
 
45
                           )
 
46
from bzrlib.lockable_files import LockableFiles, TransportLock
 
47
from bzrlib.symbol_versioning import (deprecated_function,
 
48
                                      deprecated_method,
 
49
                                      DEPRECATED_PARAMETER,
 
50
                                      deprecated_passed,
 
51
                                      zero_eight, zero_nine,
 
52
                                      )
28
53
from bzrlib.trace import mutter, note
29
 
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
30
 
                            rename, splitpath, sha_file, appendpath, 
31
 
                            file_kind)
32
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
33
 
                           NoSuchRevision, HistoryMissing, NotBranchError,
34
 
                           DivergedBranches, LockError, UnlistableStore,
35
 
                           UnlistableBranch, NoSuchFile)
36
 
from bzrlib.textui import show_status
37
 
from bzrlib.revision import Revision
38
 
from bzrlib.delta import compare_trees
39
 
from bzrlib.tree import EmptyTree, RevisionTree
40
 
from bzrlib.inventory import Inventory
41
 
from bzrlib.store import copy_all
42
 
from bzrlib.store.compressed_text import CompressedTextStore
43
 
from bzrlib.store.text import TextStore
44
 
from bzrlib.store.weave import WeaveStore
45
 
from bzrlib.transport import Transport, get_transport
46
 
import bzrlib.xml5
47
 
import bzrlib.ui
48
54
 
49
55
 
50
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
51
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
52
 
## TODO: Maybe include checks for common corruption of newlines, etc?
53
 
 
 
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
59
 
 
60
 
 
61
# TODO: Maybe include checks for common corruption of newlines, etc?
54
62
 
55
63
# TODO: Some operations like log might retrieve the same revisions
56
64
# repeatedly to calculate deltas.  We could perhaps have a weakref
57
65
# cache in memory to make this faster.  In general anything can be
58
 
# cached in memory between lock and unlock operations.
59
 
 
60
 
def find_branch(*ignored, **ignored_too):
61
 
    # XXX: leave this here for about one release, then remove it
62
 
    raise NotImplementedError('find_branch() is not supported anymore, '
63
 
                              'please use one of the new branch constructors')
64
 
def _relpath(base, path):
65
 
    """Return path relative to base, or raise exception.
66
 
 
67
 
    The path may be either an absolute path or a path relative to the
68
 
    current working directory.
69
 
 
70
 
    Lifted out of Branch.relpath for ease of testing.
71
 
 
72
 
    os.path.commonprefix (python2.4) has a bad bug that it works just
73
 
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
74
 
    avoids that problem."""
75
 
    rp = os.path.abspath(path)
76
 
 
77
 
    s = []
78
 
    head = rp
79
 
    while len(head) >= len(base):
80
 
        if head == base:
81
 
            break
82
 
        head, tail = os.path.split(head)
83
 
        if tail:
84
 
            s.insert(0, tail)
85
 
    else:
86
 
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
87
 
 
88
 
    return os.sep.join(s)
89
 
        
90
 
 
91
 
def find_branch_root(t):
92
 
    """Find the branch root enclosing the transport's base.
93
 
 
94
 
    t is a Transport object.
95
 
 
96
 
    It is not necessary that the base of t exists.
97
 
 
98
 
    Basically we keep looking up until we find the control directory or
99
 
    run into the root.  If there isn't one, raises NotBranchError.
100
 
    """
101
 
    orig_base = t.base
102
 
    while True:
103
 
        if t.has(bzrlib.BZRDIR):
104
 
            return t
105
 
        new_t = t.clone('..')
106
 
        if new_t.base == t.base:
107
 
            # reached the root, whatever that may be
108
 
            raise NotBranchError('%s is not in a branch' % orig_base)
109
 
        t = new_t
 
66
# cached in memory between lock and unlock operations. .. nb thats
 
67
# what the transaction identity map provides
110
68
 
111
69
 
112
70
######################################################################
118
76
    base
119
77
        Base directory/url of the branch.
120
78
    """
 
79
    # this is really an instance variable - FIXME move it there
 
80
    # - RBC 20060112
121
81
    base = None
122
82
 
123
83
    def __init__(self, *ignored, **ignored_too):
124
84
        raise NotImplementedError('The Branch class is abstract')
125
85
 
 
86
    def break_lock(self):
 
87
        """Break a lock if one is present from another instance.
 
88
 
 
89
        Uses the ui factory to ask for confirmation if the lock may be from
 
90
        an active process.
 
91
 
 
92
        This will probe the repository for its lock as well.
 
93
        """
 
94
        self.control_files.break_lock()
 
95
        self.repository.break_lock()
 
96
        master = self.get_master_branch()
 
97
        if master is not None:
 
98
            master.break_lock()
 
99
 
126
100
    @staticmethod
 
101
    @deprecated_method(zero_eight)
127
102
    def open_downlevel(base):
128
 
        """Open a branch which may be of an old format.
129
 
        
130
 
        Only local branches are supported."""
131
 
        return _Branch(get_transport(base), relax_version_check=True)
 
103
        """Open a branch which may be of an old format."""
 
104
        return Branch.open(base, _unsupported=True)
132
105
        
133
106
    @staticmethod
134
 
    def open(base):
135
 
        """Open an existing branch, rooted at 'base' (url)"""
136
 
        t = get_transport(base)
137
 
        mutter("trying to open %r with transport %r", base, t)
138
 
        return _Branch(t)
 
107
    def open(base, _unsupported=False):
 
108
        """Open the branch rooted at base.
 
109
 
 
110
        For instance, if the branch is at URL/.bzr/branch,
 
111
        Branch.open(URL) -> a Branch instance.
 
112
        """
 
113
        control = bzrdir.BzrDir.open(base, _unsupported)
 
114
        return control.open_branch(_unsupported)
139
115
 
140
116
    @staticmethod
141
117
    def open_containing(url):
142
118
        """Open an existing branch which contains url.
143
119
        
144
120
        This probes for a branch at url, and searches upwards from there.
 
121
 
 
122
        Basically we keep looking up until we find the control directory or
 
123
        run into the root.  If there isn't one, raises NotBranchError.
 
124
        If there is one and it is either an unrecognised format or an unsupported 
 
125
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
126
        If there is one, it is returned, along with the unused portion of url.
145
127
        """
146
 
        t = get_transport(url)
147
 
        t = find_branch_root(t)
148
 
        return _Branch(t)
 
128
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
129
        return control.open_branch(), relpath
149
130
 
150
131
    @staticmethod
 
132
    @deprecated_function(zero_eight)
151
133
    def initialize(base):
152
 
        """Create a new branch, rooted at 'base' (url)"""
153
 
        t = get_transport(base)
154
 
        return _Branch(t, init=True)
 
134
        """Create a new working tree and branch, rooted at 'base' (url)
 
135
 
 
136
        NOTE: This will soon be deprecated in favour of creation
 
137
        through a BzrDir.
 
138
        """
 
139
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
155
140
 
156
141
    def setup_caching(self, cache_root):
157
142
        """Subclasses that care about caching should override this, and set
158
143
        up cached stores located under cache_root.
159
144
        """
 
145
        # seems to be unused, 2006-01-13 mbp
 
146
        warn('%s is deprecated' % self.setup_caching)
160
147
        self.cache_root = cache_root
161
148
 
162
 
 
163
 
class _Branch(Branch):
164
 
    """A branch stored in the actual filesystem.
165
 
 
166
 
    Note that it's "local" in the context of the filesystem; it doesn't
167
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
168
 
    it's writable, and can be accessed via the normal filesystem API.
169
 
 
170
 
    _lock_mode
171
 
        None, or 'r' or 'w'
172
 
 
173
 
    _lock_count
174
 
        If _lock_mode is true, a positive count of the number of times the
175
 
        lock has been taken.
176
 
 
177
 
    _lock
178
 
        Lock object from bzrlib.lock.
179
 
    """
180
 
    # We actually expect this class to be somewhat short-lived; part of its
181
 
    # purpose is to try to isolate what bits of the branch logic are tied to
182
 
    # filesystem access, so that in a later step, we can extricate them to
183
 
    # a separarte ("storage") class.
184
 
    _lock_mode = None
185
 
    _lock_count = None
186
 
    _lock = None
187
 
    _inventory_weave = None
188
 
    
189
 
    # Map some sort of prefix into a namespace
190
 
    # stuff like "revno:10", "revid:", etc.
191
 
    # This should match a prefix with a function which accepts
192
 
    REVISION_NAMESPACES = {}
193
 
 
194
 
    def push_stores(self, branch_to):
195
 
        """Copy the content of this branches store to branch_to."""
196
 
        if (self._branch_format != branch_to._branch_format
197
 
            or self._branch_format != 4):
198
 
            from bzrlib.fetch import greedy_fetch
199
 
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
200
 
                   self, self._branch_format, branch_to, branch_to._branch_format)
201
 
            greedy_fetch(to_branch=branch_to, from_branch=self,
202
 
                         revision=self.last_revision())
203
 
            return
204
 
 
205
 
        store_pairs = ((self.text_store,      branch_to.text_store),
206
 
                       (self.inventory_store, branch_to.inventory_store),
207
 
                       (self.revision_store,  branch_to.revision_store))
208
 
        try:
209
 
            for from_store, to_store in store_pairs: 
210
 
                copy_all(from_store, to_store)
211
 
        except UnlistableStore:
212
 
            raise UnlistableBranch(from_store)
213
 
 
214
 
    def __init__(self, transport, init=False,
215
 
                 relax_version_check=False):
216
 
        """Create new branch object at a particular location.
217
 
 
218
 
        transport -- A Transport object, defining how to access files.
219
 
                (If a string, transport.transport() will be used to
220
 
                create a Transport object)
221
 
        
222
 
        init -- If True, create new control files in a previously
223
 
             unversioned directory.  If False, the branch must already
224
 
             be versioned.
225
 
 
226
 
        relax_version_check -- If true, the usual check for the branch
227
 
            version is not applied.  This is intended only for
228
 
            upgrade/recovery type use; it's not guaranteed that
229
 
            all operations will work on old format branches.
230
 
 
231
 
        In the test suite, creation of new trees is tested using the
232
 
        `ScratchBranch` class.
233
 
        """
234
 
        assert isinstance(transport, Transport), \
235
 
            "%r is not a Transport" % transport
236
 
        self._transport = transport
237
 
        if init:
238
 
            self._make_control()
239
 
        self._check_format(relax_version_check)
240
 
 
241
 
        def get_store(name, compressed=True):
242
 
            # FIXME: This approach of assuming stores are all entirely compressed
243
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
244
 
            # some existing branches where there's a mixture; we probably 
245
 
            # still want the option to look for both.
246
 
            relpath = self._rel_controlfilename(name)
247
 
            if compressed:
248
 
                store = CompressedTextStore(self._transport.clone(relpath))
249
 
            else:
250
 
                store = TextStore(self._transport.clone(relpath))
251
 
            #if self._transport.should_cache():
252
 
            #    cache_path = os.path.join(self.cache_root, name)
253
 
            #    os.mkdir(cache_path)
254
 
            #    store = bzrlib.store.CachedStore(store, cache_path)
255
 
            return store
256
 
        def get_weave(name):
257
 
            relpath = self._rel_controlfilename(name)
258
 
            ws = WeaveStore(self._transport.clone(relpath))
259
 
            if self._transport.should_cache():
260
 
                ws.enable_cache = True
261
 
            return ws
262
 
 
263
 
        if self._branch_format == 4:
264
 
            self.inventory_store = get_store('inventory-store')
265
 
            self.text_store = get_store('text-store')
266
 
            self.revision_store = get_store('revision-store')
267
 
        elif self._branch_format == 5:
268
 
            self.control_weaves = get_weave([])
269
 
            self.weave_store = get_weave('weaves')
270
 
            self.revision_store = get_store('revision-store', compressed=False)
271
 
 
272
 
    def __str__(self):
273
 
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
274
 
 
275
 
 
276
 
    __repr__ = __str__
277
 
 
278
 
 
279
 
    def __del__(self):
280
 
        if self._lock_mode or self._lock:
281
 
            # XXX: This should show something every time, and be suitable for
282
 
            # headless operation and embedding
283
 
            warn("branch %r was not explicitly unlocked" % self)
284
 
            self._lock.unlock()
285
 
 
286
 
        # TODO: It might be best to do this somewhere else,
287
 
        # but it is nice for a Branch object to automatically
288
 
        # cache it's information.
289
 
        # Alternatively, we could have the Transport objects cache requests
290
 
        # See the earlier discussion about how major objects (like Branch)
291
 
        # should never expect their __del__ function to run.
292
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
293
 
            try:
294
 
                import shutil
295
 
                shutil.rmtree(self.cache_root)
296
 
            except:
297
 
                pass
298
 
            self.cache_root = None
299
 
 
300
 
    def _get_base(self):
301
 
        if self._transport:
302
 
            return self._transport.base
303
 
        return None
304
 
 
305
 
    base = property(_get_base)
306
 
 
 
149
    def get_config(self):
 
150
        return bzrlib.config.BranchConfig(self)
 
151
 
 
152
    def _get_nick(self):
 
153
        return self.get_config().get_nickname()
 
154
 
 
155
    def _set_nick(self, nick):
 
156
        self.get_config().set_user_option('nickname', nick)
 
157
 
 
158
    nick = property(_get_nick, _set_nick)
 
159
 
 
160
    def is_locked(self):
 
161
        raise NotImplementedError(self.is_locked)
307
162
 
308
163
    def lock_write(self):
309
 
        # TODO: Upgrade locking to support using a Transport,
310
 
        # and potentially a remote locking protocol
311
 
        if self._lock_mode:
312
 
            if self._lock_mode != 'w':
313
 
                raise LockError("can't upgrade to a write lock from %r" %
314
 
                                self._lock_mode)
315
 
            self._lock_count += 1
316
 
        else:
317
 
            self._lock = self._transport.lock_write(
318
 
                    self._rel_controlfilename('branch-lock'))
319
 
            self._lock_mode = 'w'
320
 
            self._lock_count = 1
321
 
 
 
164
        raise NotImplementedError(self.lock_write)
322
165
 
323
166
    def lock_read(self):
324
 
        if self._lock_mode:
325
 
            assert self._lock_mode in ('r', 'w'), \
326
 
                   "invalid lock mode %r" % self._lock_mode
327
 
            self._lock_count += 1
328
 
        else:
329
 
            self._lock = self._transport.lock_read(
330
 
                    self._rel_controlfilename('branch-lock'))
331
 
            self._lock_mode = 'r'
332
 
            self._lock_count = 1
333
 
                        
 
167
        raise NotImplementedError(self.lock_read)
 
168
 
334
169
    def unlock(self):
335
 
        if not self._lock_mode:
336
 
            raise LockError('branch %r is not locked' % (self))
337
 
 
338
 
        if self._lock_count > 1:
339
 
            self._lock_count -= 1
340
 
        else:
341
 
            self._lock.unlock()
342
 
            self._lock = None
343
 
            self._lock_mode = self._lock_count = None
 
170
        raise NotImplementedError(self.unlock)
 
171
 
 
172
    def peek_lock_mode(self):
 
173
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
174
        raise NotImplementedError(self.peek_lock_mode)
 
175
 
 
176
    def get_physical_lock_status(self):
 
177
        raise NotImplementedError(self.get_physical_lock_status)
344
178
 
345
179
    def abspath(self, name):
346
 
        """Return absolute filename for something in the branch"""
347
 
        return self._transport.abspath(name)
348
 
 
349
 
    def relpath(self, path):
350
 
        """Return path relative to this branch of something inside it.
351
 
 
352
 
        Raises an error if path is not in this branch."""
353
 
        return self._transport.relpath(path)
354
 
 
355
 
 
356
 
    def _rel_controlfilename(self, file_or_path):
357
 
        if isinstance(file_or_path, basestring):
358
 
            file_or_path = [file_or_path]
359
 
        return [bzrlib.BZRDIR] + file_or_path
360
 
 
361
 
    def controlfilename(self, file_or_path):
362
 
        """Return location relative to branch."""
363
 
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
364
 
 
365
 
 
366
 
    def controlfile(self, file_or_path, mode='r'):
367
 
        """Open a control file for this branch.
368
 
 
369
 
        There are two classes of file in the control directory: text
370
 
        and binary.  binary files are untranslated byte streams.  Text
371
 
        control files are stored with Unix newlines and in UTF-8, even
372
 
        if the platform or locale defaults are different.
373
 
 
374
 
        Controlfiles should almost never be opened in write mode but
375
 
        rather should be atomically copied and replaced using atomicfile.
376
 
        """
377
 
        import codecs
378
 
 
379
 
        relpath = self._rel_controlfilename(file_or_path)
380
 
        #TODO: codecs.open() buffers linewise, so it was overloaded with
381
 
        # a much larger buffer, do we need to do the same for getreader/getwriter?
382
 
        if mode == 'rb': 
383
 
            return self._transport.get(relpath)
384
 
        elif mode == 'wb':
385
 
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
386
 
        elif mode == 'r':
387
 
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
388
 
        elif mode == 'w':
389
 
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
 
180
        """Return absolute filename for something in the branch
 
181
        
 
182
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
183
        method and not a tree method.
 
184
        """
 
185
        raise NotImplementedError(self.abspath)
 
186
 
 
187
    def bind(self, other):
 
188
        """Bind the local branch the other branch.
 
189
 
 
190
        :param other: The branch to bind to
 
191
        :type other: Branch
 
192
        """
 
193
        raise errors.UpgradeRequired(self.base)
 
194
 
 
195
    @needs_write_lock
 
196
    def fetch(self, from_branch, last_revision=None, pb=None):
 
197
        """Copy revisions from from_branch into this branch.
 
198
 
 
199
        :param from_branch: Where to copy from.
 
200
        :param last_revision: What revision to stop at (None for at the end
 
201
                              of the branch.
 
202
        :param pb: An optional progress bar to use.
 
203
 
 
204
        Returns the copied revision count and the failed revisions in a tuple:
 
205
        (copied, failures).
 
206
        """
 
207
        if self.base == from_branch.base:
 
208
            return (0, [])
 
209
        if pb is None:
 
210
            nested_pb = ui.ui_factory.nested_progress_bar()
 
211
            pb = nested_pb
390
212
        else:
391
 
            raise BzrError("invalid controlfile mode %r" % mode)
392
 
 
393
 
    def put_controlfile(self, path, f, encode=True):
394
 
        """Write an entry as a controlfile.
395
 
 
396
 
        :param path: The path to put the file, relative to the .bzr control
397
 
                     directory
398
 
        :param f: A file-like or string object whose contents should be copied.
399
 
        :param encode:  If true, encode the contents as utf-8
400
 
        """
401
 
        self.put_controlfiles([(path, f)], encode=encode)
402
 
 
403
 
    def put_controlfiles(self, files, encode=True):
404
 
        """Write several entries as controlfiles.
405
 
 
406
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
407
 
                      underneath the bzr control directory
408
 
        :param encode:  If true, encode the contents as utf-8
409
 
        """
410
 
        import codecs
411
 
        ctrl_files = []
412
 
        for path, f in files:
413
 
            if encode:
414
 
                if isinstance(f, basestring):
415
 
                    f = f.encode('utf-8', 'replace')
 
213
            nested_pb = None
 
214
 
 
215
        from_branch.lock_read()
 
216
        try:
 
217
            if last_revision is None:
 
218
                pb.update('get source history')
 
219
                from_history = from_branch.revision_history()
 
220
                if from_history:
 
221
                    last_revision = from_history[-1]
416
222
                else:
417
 
                    f = codecs.getwriter('utf-8')(f, errors='replace')
418
 
            path = self._rel_controlfilename(path)
419
 
            ctrl_files.append((path, f))
420
 
        self._transport.put_multi(ctrl_files)
421
 
 
422
 
    def _make_control(self):
423
 
        from bzrlib.inventory import Inventory
424
 
        from bzrlib.weavefile import write_weave_v5
425
 
        from bzrlib.weave import Weave
426
 
        
427
 
        # Create an empty inventory
428
 
        sio = StringIO()
429
 
        # if we want per-tree root ids then this is the place to set
430
 
        # them; they're not needed for now and so ommitted for
431
 
        # simplicity.
432
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
433
 
        empty_inv = sio.getvalue()
434
 
        sio = StringIO()
435
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
436
 
        empty_weave = sio.getvalue()
437
 
 
438
 
        dirs = [[], 'revision-store', 'weaves']
439
 
        files = [('README', 
440
 
            "This is a Bazaar-NG control directory.\n"
441
 
            "Do not change any files in this directory.\n"),
442
 
            ('branch-format', BZR_BRANCH_FORMAT_5),
443
 
            ('revision-history', ''),
444
 
            ('branch-name', ''),
445
 
            ('branch-lock', ''),
446
 
            ('pending-merges', ''),
447
 
            ('inventory', empty_inv),
448
 
            ('inventory.weave', empty_weave),
449
 
            ('ancestry.weave', empty_weave)
450
 
        ]
451
 
        cfn = self._rel_controlfilename
452
 
        self._transport.mkdir_multi([cfn(d) for d in dirs])
453
 
        self.put_controlfiles(files)
454
 
        mutter('created control directory in ' + self._transport.base)
455
 
 
456
 
    def _check_format(self, relax_version_check):
457
 
        """Check this branch format is supported.
458
 
 
459
 
        The format level is stored, as an integer, in
460
 
        self._branch_format for code that needs to check it later.
461
 
 
462
 
        In the future, we might need different in-memory Branch
463
 
        classes to support downlevel branches.  But not yet.
464
 
        """
465
 
        try:
466
 
            fmt = self.controlfile('branch-format', 'r').read()
467
 
        except NoSuchFile:
468
 
            raise NotBranchError(self.base)
469
 
        mutter("got branch format %r", fmt)
470
 
        if fmt == BZR_BRANCH_FORMAT_5:
471
 
            self._branch_format = 5
472
 
        elif fmt == BZR_BRANCH_FORMAT_4:
473
 
            self._branch_format = 4
474
 
 
475
 
        if (not relax_version_check
476
 
            and self._branch_format != 5):
477
 
            raise BzrError('sorry, branch format %r not supported' % fmt,
478
 
                           ['use a different bzr version',
479
 
                            'or remove the .bzr directory'
480
 
                            ' and "bzr init" again'])
481
 
 
482
 
    def get_root_id(self):
483
 
        """Return the id of this branches root"""
484
 
        inv = self.read_working_inventory()
485
 
        return inv.root.file_id
486
 
 
487
 
    def set_root_id(self, file_id):
488
 
        inv = self.read_working_inventory()
489
 
        orig_root_id = inv.root.file_id
490
 
        del inv._byid[inv.root.file_id]
491
 
        inv.root.file_id = file_id
492
 
        inv._byid[inv.root.file_id] = inv.root
493
 
        for fid in inv:
494
 
            entry = inv[fid]
495
 
            if entry.parent_id in (None, orig_root_id):
496
 
                entry.parent_id = inv.root.file_id
497
 
        self._write_inventory(inv)
498
 
 
499
 
    def read_working_inventory(self):
500
 
        """Read the working inventory."""
501
 
        self.lock_read()
502
 
        try:
503
 
            # ElementTree does its own conversion from UTF-8, so open in
504
 
            # binary.
505
 
            f = self.controlfile('inventory', 'rb')
506
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
507
 
        finally:
508
 
            self.unlock()
509
 
            
510
 
 
511
 
    def _write_inventory(self, inv):
512
 
        """Update the working inventory.
513
 
 
514
 
        That is to say, the inventory describing changes underway, that
515
 
        will be committed to the next revision.
516
 
        """
517
 
        from cStringIO import StringIO
518
 
        self.lock_write()
519
 
        try:
520
 
            sio = StringIO()
521
 
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
522
 
            sio.seek(0)
523
 
            # Transport handles atomicity
524
 
            self.put_controlfile('inventory', sio)
525
 
        finally:
526
 
            self.unlock()
527
 
        
528
 
        mutter('wrote working inventory')
529
 
            
530
 
    inventory = property(read_working_inventory, _write_inventory, None,
531
 
                         """Inventory for the working copy.""")
532
 
 
533
 
    def add(self, files, ids=None):
534
 
        """Make files versioned.
535
 
 
536
 
        Note that the command line normally calls smart_add instead,
537
 
        which can automatically recurse.
538
 
 
539
 
        This puts the files in the Added state, so that they will be
540
 
        recorded by the next commit.
541
 
 
542
 
        files
543
 
            List of paths to add, relative to the base of the tree.
544
 
 
545
 
        ids
546
 
            If set, use these instead of automatically generated ids.
547
 
            Must be the same length as the list of files, but may
548
 
            contain None for ids that are to be autogenerated.
549
 
 
550
 
        TODO: Perhaps have an option to add the ids even if the files do
551
 
              not (yet) exist.
552
 
 
553
 
        TODO: Perhaps yield the ids and paths as they're added.
554
 
        """
555
 
        # TODO: Re-adding a file that is removed in the working copy
556
 
        # should probably put it back with the previous ID.
557
 
        if isinstance(files, basestring):
558
 
            assert(ids is None or isinstance(ids, basestring))
559
 
            files = [files]
560
 
            if ids is not None:
561
 
                ids = [ids]
562
 
 
563
 
        if ids is None:
564
 
            ids = [None] * len(files)
565
 
        else:
566
 
            assert(len(ids) == len(files))
567
 
 
568
 
        self.lock_write()
569
 
        try:
570
 
            inv = self.read_working_inventory()
571
 
            for f,file_id in zip(files, ids):
572
 
                if is_control_file(f):
573
 
                    raise BzrError("cannot add control file %s" % quotefn(f))
574
 
 
575
 
                fp = splitpath(f)
576
 
 
577
 
                if len(fp) == 0:
578
 
                    raise BzrError("cannot add top-level %r" % f)
579
 
 
580
 
                fullpath = os.path.normpath(self.abspath(f))
581
 
 
582
 
                try:
583
 
                    kind = file_kind(fullpath)
584
 
                except OSError:
585
 
                    # maybe something better?
586
 
                    raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
587
 
 
588
 
                if not InventoryEntry.versionable_kind(kind):
589
 
                    raise BzrError('cannot add: not a versionable file ('
590
 
                                   'i.e. regular file, symlink or directory): %s' % quotefn(f))
591
 
 
592
 
                if file_id is None:
593
 
                    file_id = gen_file_id(f)
594
 
                inv.add_path(f, kind=kind, file_id=file_id)
595
 
 
596
 
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
597
 
 
598
 
            self._write_inventory(inv)
599
 
        finally:
600
 
            self.unlock()
601
 
            
602
 
 
603
 
    def print_file(self, file, revno):
604
 
        """Print `file` to stdout."""
605
 
        self.lock_read()
606
 
        try:
607
 
            tree = self.revision_tree(self.get_rev_id(revno))
608
 
            # use inventory as it was in that revision
609
 
            file_id = tree.inventory.path2id(file)
610
 
            if not file_id:
611
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
612
 
            tree.print_file(file_id)
613
 
        finally:
614
 
            self.unlock()
615
 
 
616
 
 
617
 
    def remove(self, files, verbose=False):
618
 
        """Mark nominated files for removal from the inventory.
619
 
 
620
 
        This does not remove their text.  This does not run on 
621
 
 
622
 
        TODO: Refuse to remove modified files unless --force is given?
623
 
 
624
 
        TODO: Do something useful with directories.
625
 
 
626
 
        TODO: Should this remove the text or not?  Tough call; not
627
 
        removing may be useful and the user can just use use rm, and
628
 
        is the opposite of add.  Removing it is consistent with most
629
 
        other tools.  Maybe an option.
630
 
        """
631
 
        ## TODO: Normalize names
632
 
        ## TODO: Remove nested loops; better scalability
633
 
        if isinstance(files, basestring):
634
 
            files = [files]
635
 
 
636
 
        self.lock_write()
637
 
 
638
 
        try:
639
 
            tree = self.working_tree()
640
 
            inv = tree.inventory
641
 
 
642
 
            # do this before any modifications
643
 
            for f in files:
644
 
                fid = inv.path2id(f)
645
 
                if not fid:
646
 
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
647
 
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
648
 
                if verbose:
649
 
                    # having remove it, it must be either ignored or unknown
650
 
                    if tree.is_ignored(f):
651
 
                        new_status = 'I'
652
 
                    else:
653
 
                        new_status = '?'
654
 
                    show_status(new_status, inv[fid].kind, quotefn(f))
655
 
                del inv[fid]
656
 
 
657
 
            self._write_inventory(inv)
658
 
        finally:
659
 
            self.unlock()
660
 
 
661
 
    # FIXME: this doesn't need to be a branch method
662
 
    def set_inventory(self, new_inventory_list):
663
 
        from bzrlib.inventory import Inventory, InventoryEntry
664
 
        inv = Inventory(self.get_root_id())
665
 
        for path, file_id, parent, kind in new_inventory_list:
666
 
            name = os.path.basename(path)
667
 
            if name == "":
668
 
                continue
669
 
            # fixme, there should be a factory function inv,add_?? 
670
 
            if kind == 'directory':
671
 
                inv.add(inventory.InventoryDirectory(file_id, name, parent))
672
 
            elif kind == 'file':
673
 
                inv.add(inventory.InventoryFile(file_id, name, parent))
674
 
            elif kind == 'symlink':
675
 
                inv.add(inventory.InventoryLink(file_id, name, parent))
676
 
            else:
677
 
                raise BzrError("unknown kind %r" % kind)
678
 
        self._write_inventory(inv)
679
 
 
680
 
    def unknowns(self):
681
 
        """Return all unknown files.
682
 
 
683
 
        These are files in the working directory that are not versioned or
684
 
        control files or ignored.
685
 
        
686
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
687
 
        >>> list(b.unknowns())
688
 
        ['foo']
689
 
        >>> b.add('foo')
690
 
        >>> list(b.unknowns())
691
 
        []
692
 
        >>> b.remove('foo')
693
 
        >>> list(b.unknowns())
694
 
        ['foo']
695
 
        """
696
 
        return self.working_tree().unknowns()
697
 
 
698
 
 
699
 
    def append_revision(self, *revision_ids):
700
 
        for revision_id in revision_ids:
701
 
            mutter("add {%s} to revision-history" % revision_id)
702
 
        self.lock_write()
703
 
        try:
704
 
            rev_history = self.revision_history()
705
 
            rev_history.extend(revision_ids)
706
 
            self.put_controlfile('revision-history', '\n'.join(rev_history))
707
 
        finally:
708
 
            self.unlock()
709
 
 
710
 
    def has_revision(self, revision_id):
711
 
        """True if this branch has a copy of the revision.
712
 
 
713
 
        This does not necessarily imply the revision is merge
714
 
        or on the mainline."""
715
 
        return (revision_id is None
716
 
                or revision_id in self.revision_store)
717
 
 
718
 
    def get_revision_xml_file(self, revision_id):
719
 
        """Return XML file object for revision object."""
720
 
        if not revision_id or not isinstance(revision_id, basestring):
721
 
            raise InvalidRevisionId(revision_id)
722
 
 
723
 
        self.lock_read()
724
 
        try:
725
 
            try:
726
 
                return self.revision_store[revision_id]
727
 
            except (IndexError, KeyError):
728
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
729
 
        finally:
730
 
            self.unlock()
731
 
 
732
 
    #deprecated
733
 
    get_revision_xml = get_revision_xml_file
734
 
 
735
 
    def get_revision_xml(self, revision_id):
736
 
        return self.get_revision_xml_file(revision_id).read()
737
 
 
738
 
 
739
 
    def get_revision(self, revision_id):
740
 
        """Return the Revision object for a named revision"""
741
 
        xml_file = self.get_revision_xml_file(revision_id)
742
 
 
743
 
        try:
744
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
745
 
        except SyntaxError, e:
746
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
747
 
                                         [revision_id,
748
 
                                          str(e)])
749
 
            
750
 
        assert r.revision_id == revision_id
751
 
        return r
 
223
                    # no history in the source branch
 
224
                    last_revision = revision.NULL_REVISION
 
225
            return self.repository.fetch(from_branch.repository,
 
226
                                         revision_id=last_revision,
 
227
                                         pb=nested_pb)
 
228
        finally:
 
229
            if nested_pb is not None:
 
230
                nested_pb.finished()
 
231
            from_branch.unlock()
 
232
 
 
233
    def get_bound_location(self):
 
234
        """Return the URL of the branch we are bound to.
 
235
 
 
236
        Older format branches cannot bind, please be sure to use a metadir
 
237
        branch.
 
238
        """
 
239
        return None
 
240
    
 
241
    def get_commit_builder(self, parents, config=None, timestamp=None, 
 
242
                           timezone=None, committer=None, revprops=None, 
 
243
                           revision_id=None):
 
244
        """Obtain a CommitBuilder for this branch.
 
245
        
 
246
        :param parents: Revision ids of the parents of the new revision.
 
247
        :param config: Optional configuration to use.
 
248
        :param timestamp: Optional timestamp recorded for commit.
 
249
        :param timezone: Optional timezone for timestamp.
 
250
        :param committer: Optional committer to set for commit.
 
251
        :param revprops: Optional dictionary of revision properties.
 
252
        :param revision_id: Optional revision id.
 
253
        """
 
254
 
 
255
        if config is None:
 
256
            config = self.get_config()
 
257
        
 
258
        return self.repository.get_commit_builder(self, parents, config, 
 
259
            timestamp, timezone, committer, revprops, revision_id)
 
260
 
 
261
    def get_master_branch(self):
 
262
        """Return the branch we are bound to.
 
263
        
 
264
        :return: Either a Branch, or None
 
265
        """
 
266
        return None
752
267
 
753
268
    def get_revision_delta(self, revno):
754
269
        """Return the delta for one revision.
760
275
        rh = self.revision_history()
761
276
        if not (1 <= revno <= len(rh)):
762
277
            raise InvalidRevisionNumber(revno)
763
 
 
764
 
        # revno is 1-based; list is 0-based
765
 
 
766
 
        new_tree = self.revision_tree(rh[revno-1])
767
 
        if revno == 1:
768
 
            old_tree = EmptyTree()
769
 
        else:
770
 
            old_tree = self.revision_tree(rh[revno-2])
771
 
 
772
 
        return compare_trees(old_tree, new_tree)
773
 
 
774
 
    def get_revision_sha1(self, revision_id):
775
 
        """Hash the stored value of a revision, and return it."""
776
 
        # In the future, revision entries will be signed. At that
777
 
        # point, it is probably best *not* to include the signature
778
 
        # in the revision hash. Because that lets you re-sign
779
 
        # the revision, (add signatures/remove signatures) and still
780
 
        # have all hash pointers stay consistent.
781
 
        # But for now, just hash the contents.
782
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
783
 
 
784
 
    def get_ancestry(self, revision_id):
785
 
        """Return a list of revision-ids integrated by a revision.
786
 
        
787
 
        This currently returns a list, but the ordering is not guaranteed:
788
 
        treat it as a set.
789
 
        """
790
 
        if revision_id is None:
791
 
            return [None]
792
 
        w = self.control_weaves.get_weave('inventory')
793
 
        return [None] + map(w.idx_to_name,
794
 
                            w.inclusions([w.lookup(revision_id)]))
795
 
 
796
 
    def get_inventory_weave(self):
797
 
        return self.control_weaves.get_weave('inventory')
798
 
 
799
 
    def get_inventory(self, revision_id):
800
 
        """Get Inventory object by hash."""
801
 
        xml = self.get_inventory_xml(revision_id)
802
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
803
 
 
804
 
    def get_inventory_xml(self, revision_id):
805
 
        """Get inventory XML as a file object."""
806
 
        try:
807
 
            assert isinstance(revision_id, basestring), type(revision_id)
808
 
            iw = self.get_inventory_weave()
809
 
            return iw.get_text(iw.lookup(revision_id))
810
 
        except IndexError:
811
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
812
 
 
813
 
    def get_inventory_sha1(self, revision_id):
814
 
        """Return the sha1 hash of the inventory entry
815
 
        """
816
 
        return self.get_revision(revision_id).inventory_sha1
817
 
 
818
 
    def get_revision_inventory(self, revision_id):
819
 
        """Return inventory of a past revision."""
820
 
        # TODO: Unify this with get_inventory()
821
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
822
 
        # must be the same as its revision, so this is trivial.
823
 
        if revision_id == None:
824
 
            return Inventory(self.get_root_id())
825
 
        else:
826
 
            return self.get_inventory(revision_id)
 
278
        return self.repository.get_revision_delta(rh[revno-1])
 
279
 
 
280
    def get_root_id(self):
 
281
        """Return the id of this branches root"""
 
282
        raise NotImplementedError(self.get_root_id)
 
283
 
 
284
    def print_file(self, file, revision_id):
 
285
        """Print `file` to stdout."""
 
286
        raise NotImplementedError(self.print_file)
 
287
 
 
288
    def append_revision(self, *revision_ids):
 
289
        raise NotImplementedError(self.append_revision)
 
290
 
 
291
    def set_revision_history(self, rev_history):
 
292
        raise NotImplementedError(self.set_revision_history)
827
293
 
828
294
    def revision_history(self):
829
295
        """Return sequence of revision hashes on to this branch."""
830
 
        self.lock_read()
831
 
        try:
832
 
            return [l.rstrip('\r\n') for l in
833
 
                    self.controlfile('revision-history', 'r').readlines()]
834
 
        finally:
835
 
            self.unlock()
836
 
 
837
 
    def common_ancestor(self, other, self_revno=None, other_revno=None):
838
 
        """
839
 
        >>> from bzrlib.commit import commit
840
 
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
841
 
        >>> sb.common_ancestor(sb) == (None, None)
842
 
        True
843
 
        >>> commit(sb, "Committing first revision", verbose=False)
844
 
        >>> sb.common_ancestor(sb)[0]
845
 
        1
846
 
        >>> clone = sb.clone()
847
 
        >>> commit(sb, "Committing second revision", verbose=False)
848
 
        >>> sb.common_ancestor(sb)[0]
849
 
        2
850
 
        >>> sb.common_ancestor(clone)[0]
851
 
        1
852
 
        >>> commit(clone, "Committing divergent second revision", 
853
 
        ...               verbose=False)
854
 
        >>> sb.common_ancestor(clone)[0]
855
 
        1
856
 
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
857
 
        True
858
 
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
859
 
        True
860
 
        >>> clone2 = sb.clone()
861
 
        >>> sb.common_ancestor(clone2)[0]
862
 
        2
863
 
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
864
 
        1
865
 
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
866
 
        1
867
 
        """
868
 
        my_history = self.revision_history()
869
 
        other_history = other.revision_history()
870
 
        if self_revno is None:
871
 
            self_revno = len(my_history)
872
 
        if other_revno is None:
873
 
            other_revno = len(other_history)
874
 
        indices = range(min((self_revno, other_revno)))
875
 
        indices.reverse()
876
 
        for r in indices:
877
 
            if my_history[r] == other_history[r]:
878
 
                return r+1, my_history[r]
879
 
        return None, None
880
 
 
 
296
        raise NotImplementedError('revision_history is abstract')
881
297
 
882
298
    def revno(self):
883
299
        """Return current revision number for this branch.
887
303
        """
888
304
        return len(self.revision_history())
889
305
 
 
306
    def unbind(self):
 
307
        """Older format branches cannot bind or unbind."""
 
308
        raise errors.UpgradeRequired(self.base)
890
309
 
891
310
    def last_revision(self):
892
 
        """Return last patch hash, or None if no history.
893
 
        """
 
311
        """Return last revision id, or None"""
894
312
        ph = self.revision_history()
895
313
        if ph:
896
314
            return ph[-1]
897
315
        else:
898
316
            return None
899
317
 
900
 
 
901
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
318
    def missing_revisions(self, other, stop_revision=None):
902
319
        """Return a list of new revisions that would perfectly fit.
903
320
        
904
321
        If self and other have not diverged, return a list of the revisions
905
322
        present in other, but missing from self.
906
 
 
907
 
        >>> from bzrlib.commit import commit
908
 
        >>> bzrlib.trace.silent = True
909
 
        >>> br1 = ScratchBranch()
910
 
        >>> br2 = ScratchBranch()
911
 
        >>> br1.missing_revisions(br2)
912
 
        []
913
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
914
 
        >>> br1.missing_revisions(br2)
915
 
        [u'REVISION-ID-1']
916
 
        >>> br2.missing_revisions(br1)
917
 
        []
918
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
919
 
        >>> br1.missing_revisions(br2)
920
 
        []
921
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
922
 
        >>> br1.missing_revisions(br2)
923
 
        [u'REVISION-ID-2A']
924
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
925
 
        >>> br1.missing_revisions(br2)
926
 
        Traceback (most recent call last):
927
 
        DivergedBranches: These branches have diverged.
928
323
        """
929
 
        # FIXME: If the branches have diverged, but the latest
930
 
        # revision in this branch is completely merged into the other,
931
 
        # then we should still be able to pull.
932
324
        self_history = self.revision_history()
933
325
        self_len = len(self_history)
934
326
        other_history = other.revision_history()
943
335
        else:
944
336
            assert isinstance(stop_revision, int)
945
337
            if stop_revision > other_len:
946
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
338
                raise errors.NoSuchRevision(self, stop_revision)
947
339
        return other_history[self_len:stop_revision]
948
340
 
949
341
    def update_revisions(self, other, stop_revision=None):
950
 
        """Pull in new perfect-fit revisions."""
951
 
        from bzrlib.fetch import greedy_fetch
952
 
        from bzrlib.revision import get_intervening_revisions
953
 
        if stop_revision is None:
954
 
            stop_revision = other.last_revision()
955
 
        greedy_fetch(to_branch=self, from_branch=other,
956
 
                     revision=stop_revision)
957
 
        pullable_revs = self.missing_revisions(
958
 
            other, other.revision_id_to_revno(stop_revision))
959
 
        if pullable_revs:
960
 
            greedy_fetch(to_branch=self,
961
 
                         from_branch=other,
962
 
                         revision=pullable_revs[-1])
963
 
            self.append_revision(*pullable_revs)
964
 
    
965
 
 
966
 
    def commit(self, *args, **kw):
967
 
        from bzrlib.commit import Commit
968
 
        Commit().commit(self, *args, **kw)
969
 
    
 
342
        """Pull in new perfect-fit revisions.
 
343
 
 
344
        :param other: Another Branch to pull from
 
345
        :param stop_revision: Updated until the given revision
 
346
        :return: None
 
347
        """
 
348
        raise NotImplementedError(self.update_revisions)
 
349
 
970
350
    def revision_id_to_revno(self, revision_id):
971
351
        """Given a revision id, return its revno"""
972
352
        if revision_id is None:
987
367
            raise bzrlib.errors.NoSuchRevision(self, revno)
988
368
        return history[revno - 1]
989
369
 
990
 
    def revision_tree(self, revision_id):
991
 
        """Return Tree for a revision on this branch.
992
 
 
993
 
        `revision_id` may be None for the null revision, in which case
994
 
        an `EmptyTree` is returned."""
995
 
        # TODO: refactor this to use an existing revision object
996
 
        # so we don't need to read it in twice.
997
 
        if revision_id == None:
998
 
            return EmptyTree()
999
 
        else:
1000
 
            inv = self.get_revision_inventory(revision_id)
1001
 
            return RevisionTree(self.weave_store, inv, revision_id)
1002
 
 
1003
 
 
1004
 
    def working_tree(self):
1005
 
        """Return a `Tree` for the working copy."""
1006
 
        from bzrlib.workingtree import WorkingTree
1007
 
        # TODO: In the future, WorkingTree should utilize Transport
1008
 
        # RobertCollins 20051003 - I don't think it should - working trees are
1009
 
        # much more complex to keep consistent than our careful .bzr subset.
1010
 
        # instead, we should say that working trees are local only, and optimise
1011
 
        # for that.
1012
 
        return WorkingTree(self._transport.base, self.read_working_inventory())
1013
 
 
 
370
    def pull(self, source, overwrite=False, stop_revision=None):
 
371
        raise NotImplementedError(self.pull)
1014
372
 
1015
373
    def basis_tree(self):
1016
 
        """Return `Tree` object for last revision.
1017
 
 
1018
 
        If there are no revisions yet, return an `EmptyTree`.
1019
 
        """
1020
 
        return self.revision_tree(self.last_revision())
1021
 
 
 
374
        """Return `Tree` object for last revision."""
 
375
        return self.repository.revision_tree(self.last_revision())
1022
376
 
1023
377
    def rename_one(self, from_rel, to_rel):
1024
378
        """Rename one file.
1025
379
 
1026
380
        This can change the directory or the filename or both.
1027
381
        """
1028
 
        self.lock_write()
1029
 
        try:
1030
 
            tree = self.working_tree()
1031
 
            inv = tree.inventory
1032
 
            if not tree.has_filename(from_rel):
1033
 
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1034
 
            if tree.has_filename(to_rel):
1035
 
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
1036
 
 
1037
 
            file_id = inv.path2id(from_rel)
1038
 
            if file_id == None:
1039
 
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1040
 
 
1041
 
            if inv.path2id(to_rel):
1042
 
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1043
 
 
1044
 
            to_dir, to_tail = os.path.split(to_rel)
1045
 
            to_dir_id = inv.path2id(to_dir)
1046
 
            if to_dir_id == None and to_dir != '':
1047
 
                raise BzrError("can't determine destination directory id for %r" % to_dir)
1048
 
 
1049
 
            mutter("rename_one:")
1050
 
            mutter("  file_id    {%s}" % file_id)
1051
 
            mutter("  from_rel   %r" % from_rel)
1052
 
            mutter("  to_rel     %r" % to_rel)
1053
 
            mutter("  to_dir     %r" % to_dir)
1054
 
            mutter("  to_dir_id  {%s}" % to_dir_id)
1055
 
 
1056
 
            inv.rename(file_id, to_dir_id, to_tail)
1057
 
 
1058
 
            from_abs = self.abspath(from_rel)
1059
 
            to_abs = self.abspath(to_rel)
1060
 
            try:
1061
 
                rename(from_abs, to_abs)
1062
 
            except OSError, e:
1063
 
                raise BzrError("failed to rename %r to %r: %s"
1064
 
                        % (from_abs, to_abs, e[1]),
1065
 
                        ["rename rolled back"])
1066
 
 
1067
 
            self._write_inventory(inv)
1068
 
        finally:
1069
 
            self.unlock()
1070
 
 
 
382
        raise NotImplementedError(self.rename_one)
1071
383
 
1072
384
    def move(self, from_paths, to_name):
1073
385
        """Rename files.
1083
395
        This returns a list of (from_path, to_path) pairs for each
1084
396
        entry that is moved.
1085
397
        """
1086
 
        result = []
1087
 
        self.lock_write()
1088
 
        try:
1089
 
            ## TODO: Option to move IDs only
1090
 
            assert not isinstance(from_paths, basestring)
1091
 
            tree = self.working_tree()
1092
 
            inv = tree.inventory
1093
 
            to_abs = self.abspath(to_name)
1094
 
            if not isdir(to_abs):
1095
 
                raise BzrError("destination %r is not a directory" % to_abs)
1096
 
            if not tree.has_filename(to_name):
1097
 
                raise BzrError("destination %r not in working directory" % to_abs)
1098
 
            to_dir_id = inv.path2id(to_name)
1099
 
            if to_dir_id == None and to_name != '':
1100
 
                raise BzrError("destination %r is not a versioned directory" % to_name)
1101
 
            to_dir_ie = inv[to_dir_id]
1102
 
            if to_dir_ie.kind not in ('directory', 'root_directory'):
1103
 
                raise BzrError("destination %r is not a directory" % to_abs)
1104
 
 
1105
 
            to_idpath = inv.get_idpath(to_dir_id)
1106
 
 
1107
 
            for f in from_paths:
1108
 
                if not tree.has_filename(f):
1109
 
                    raise BzrError("%r does not exist in working tree" % f)
1110
 
                f_id = inv.path2id(f)
1111
 
                if f_id == None:
1112
 
                    raise BzrError("%r is not versioned" % f)
1113
 
                name_tail = splitpath(f)[-1]
1114
 
                dest_path = appendpath(to_name, name_tail)
1115
 
                if tree.has_filename(dest_path):
1116
 
                    raise BzrError("destination %r already exists" % dest_path)
1117
 
                if f_id in to_idpath:
1118
 
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
1119
 
 
1120
 
            # OK, so there's a race here, it's possible that someone will
1121
 
            # create a file in this interval and then the rename might be
1122
 
            # left half-done.  But we should have caught most problems.
1123
 
 
1124
 
            for f in from_paths:
1125
 
                name_tail = splitpath(f)[-1]
1126
 
                dest_path = appendpath(to_name, name_tail)
1127
 
                result.append((f, dest_path))
1128
 
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1129
 
                try:
1130
 
                    rename(self.abspath(f), self.abspath(dest_path))
1131
 
                except OSError, e:
1132
 
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1133
 
                            ["rename rolled back"])
1134
 
 
1135
 
            self._write_inventory(inv)
1136
 
        finally:
1137
 
            self.unlock()
1138
 
 
1139
 
        return result
1140
 
 
1141
 
 
1142
 
    def revert(self, filenames, old_tree=None, backups=True):
1143
 
        """Restore selected files to the versions from a previous tree.
1144
 
 
1145
 
        backups
1146
 
            If true (default) backups are made of files before
1147
 
            they're renamed.
1148
 
        """
1149
 
        from bzrlib.errors import NotVersionedError, BzrError
1150
 
        from bzrlib.atomicfile import AtomicFile
1151
 
        from bzrlib.osutils import backup_file
1152
 
        
1153
 
        inv = self.read_working_inventory()
1154
 
        if old_tree is None:
1155
 
            old_tree = self.basis_tree()
1156
 
        old_inv = old_tree.inventory
1157
 
 
1158
 
        nids = []
1159
 
        for fn in filenames:
1160
 
            file_id = inv.path2id(fn)
1161
 
            if not file_id:
1162
 
                raise NotVersionedError("not a versioned file", fn)
1163
 
            if not old_inv.has_id(file_id):
1164
 
                raise BzrError("file not present in old tree", fn, file_id)
1165
 
            nids.append((fn, file_id))
1166
 
            
1167
 
        # TODO: Rename back if it was previously at a different location
1168
 
 
1169
 
        # TODO: If given a directory, restore the entire contents from
1170
 
        # the previous version.
1171
 
 
1172
 
        # TODO: Make a backup to a temporary file.
1173
 
 
1174
 
        # TODO: If the file previously didn't exist, delete it?
1175
 
        for fn, file_id in nids:
1176
 
            backup_file(fn)
1177
 
            
1178
 
            f = AtomicFile(fn, 'wb')
1179
 
            try:
1180
 
                f.write(old_tree.get_file(file_id).read())
1181
 
                f.commit()
1182
 
            finally:
1183
 
                f.close()
1184
 
 
1185
 
 
1186
 
    def pending_merges(self):
1187
 
        """Return a list of pending merges.
1188
 
 
1189
 
        These are revisions that have been merged into the working
1190
 
        directory but not yet committed.
1191
 
        """
1192
 
        cfn = self._rel_controlfilename('pending-merges')
1193
 
        if not self._transport.has(cfn):
1194
 
            return []
1195
 
        p = []
1196
 
        for l in self.controlfile('pending-merges', 'r').readlines():
1197
 
            p.append(l.rstrip('\n'))
1198
 
        return p
1199
 
 
1200
 
 
1201
 
    def add_pending_merge(self, *revision_ids):
1202
 
        # TODO: Perhaps should check at this point that the
1203
 
        # history of the revision is actually present?
1204
 
        p = self.pending_merges()
1205
 
        updated = False
1206
 
        for rev_id in revision_ids:
1207
 
            if rev_id in p:
1208
 
                continue
1209
 
            p.append(rev_id)
1210
 
            updated = True
1211
 
        if updated:
1212
 
            self.set_pending_merges(p)
1213
 
 
1214
 
    def set_pending_merges(self, rev_list):
1215
 
        self.lock_write()
1216
 
        try:
1217
 
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1218
 
        finally:
1219
 
            self.unlock()
1220
 
 
 
398
        raise NotImplementedError(self.move)
1221
399
 
1222
400
    def get_parent(self):
1223
401
        """Return the parent location of the branch.
1226
404
        pattern is that the user can override it by specifying a
1227
405
        location.
1228
406
        """
1229
 
        import errno
1230
 
        _locs = ['parent', 'pull', 'x-pull']
1231
 
        for l in _locs:
1232
 
            try:
1233
 
                return self.controlfile(l, 'r').read().strip('\n')
1234
 
            except IOError, e:
1235
 
                if e.errno != errno.ENOENT:
1236
 
                    raise
 
407
        raise NotImplementedError(self.get_parent)
 
408
 
 
409
    def get_submit_branch(self):
 
410
        """Return the submit location of the branch.
 
411
 
 
412
        This is the default location for bundle.  The usual
 
413
        pattern is that the user can override it by specifying a
 
414
        location.
 
415
        """
 
416
        return self.get_config().get_user_option('submit_branch')
 
417
 
 
418
    def set_submit_branch(self, location):
 
419
        """Return the submit location of the branch.
 
420
 
 
421
        This is the default location for bundle.  The usual
 
422
        pattern is that the user can override it by specifying a
 
423
        location.
 
424
        """
 
425
        self.get_config().set_user_option('submit_branch', location)
 
426
 
 
427
    def get_push_location(self):
 
428
        """Return the None or the location to push this branch to."""
 
429
        raise NotImplementedError(self.get_push_location)
 
430
 
 
431
    def set_push_location(self, location):
 
432
        """Set a new push location for this branch."""
 
433
        raise NotImplementedError(self.set_push_location)
 
434
 
 
435
    def set_parent(self, url):
 
436
        raise NotImplementedError(self.set_parent)
 
437
 
 
438
    @needs_write_lock
 
439
    def update(self):
 
440
        """Synchronise this branch with the master branch if any. 
 
441
 
 
442
        :return: None or the last_revision pivoted out during the update.
 
443
        """
1237
444
        return None
1238
445
 
1239
 
 
1240
 
    def set_parent(self, url):
1241
 
        # TODO: Maybe delete old location files?
1242
 
        from bzrlib.atomicfile import AtomicFile
1243
 
        self.lock_write()
1244
 
        try:
1245
 
            f = AtomicFile(self.controlfilename('parent'))
1246
 
            try:
1247
 
                f.write(url + '\n')
1248
 
                f.commit()
1249
 
            finally:
1250
 
                f.close()
1251
 
        finally:
1252
 
            self.unlock()
1253
 
 
1254
446
    def check_revno(self, revno):
1255
447
        """\
1256
448
        Check whether a revno corresponds to any revision.
1266
458
        """
1267
459
        if revno < 1 or revno > self.revno():
1268
460
            raise InvalidRevisionNumber(revno)
1269
 
        
1270
 
        
1271
 
        
1272
 
 
1273
 
 
1274
 
class ScratchBranch(_Branch):
1275
 
    """Special test class: a branch that cleans up after itself.
1276
 
 
1277
 
    >>> b = ScratchBranch()
1278
 
    >>> isdir(b.base)
1279
 
    True
1280
 
    >>> bd = b.base
1281
 
    >>> b.destroy()
1282
 
    >>> isdir(bd)
1283
 
    False
1284
 
    """
1285
 
    def __init__(self, files=[], dirs=[], base=None):
1286
 
        """Make a test branch.
1287
 
 
1288
 
        This creates a temporary directory and runs init-tree in it.
1289
 
 
1290
 
        If any files are listed, they are created in the working copy.
1291
 
        """
1292
 
        from tempfile import mkdtemp
1293
 
        init = False
1294
 
        if base is None:
1295
 
            base = mkdtemp()
1296
 
            init = True
1297
 
        if isinstance(base, basestring):
1298
 
            base = get_transport(base)
1299
 
        _Branch.__init__(self, base, init=init)
1300
 
        for d in dirs:
1301
 
            self._transport.mkdir(d)
1302
 
            
1303
 
        for f in files:
1304
 
            self._transport.put(f, 'content of %s' % f)
1305
 
 
1306
 
 
1307
 
    def clone(self):
1308
 
        """
1309
 
        >>> orig = ScratchBranch(files=["file1", "file2"])
1310
 
        >>> clone = orig.clone()
1311
 
        >>> if os.name != 'nt':
1312
 
        ...   os.path.samefile(orig.base, clone.base)
1313
 
        ... else:
1314
 
        ...   orig.base == clone.base
1315
 
        ...
1316
 
        False
1317
 
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1318
 
        True
1319
 
        """
1320
 
        from shutil import copytree
1321
 
        from tempfile import mkdtemp
1322
 
        base = mkdtemp()
1323
 
        os.rmdir(base)
1324
 
        copytree(self.base, base, symlinks=True)
1325
 
        return ScratchBranch(base=base)
 
461
 
 
462
    @needs_read_lock
 
463
    def clone(self, *args, **kwargs):
 
464
        """Clone this branch into to_bzrdir preserving all semantic values.
 
465
        
 
466
        revision_id: if not None, the revision history in the new branch will
 
467
                     be truncated to end with revision_id.
 
468
        """
 
469
        # for API compatibility, until 0.8 releases we provide the old api:
 
470
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
471
        # after 0.8 releases, the *args and **kwargs should be changed:
 
472
        # def clone(self, to_bzrdir, revision_id=None):
 
473
        if (kwargs.get('to_location', None) or
 
474
            kwargs.get('revision', None) or
 
475
            kwargs.get('basis_branch', None) or
 
476
            (len(args) and isinstance(args[0], basestring))):
 
477
            # backwards compatibility api:
 
478
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
479
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
480
            # get basis_branch
 
481
            if len(args) > 2:
 
482
                basis_branch = args[2]
 
483
            else:
 
484
                basis_branch = kwargs.get('basis_branch', None)
 
485
            if basis_branch:
 
486
                basis = basis_branch.bzrdir
 
487
            else:
 
488
                basis = None
 
489
            # get revision
 
490
            if len(args) > 1:
 
491
                revision_id = args[1]
 
492
            else:
 
493
                revision_id = kwargs.get('revision', None)
 
494
            # get location
 
495
            if len(args):
 
496
                url = args[0]
 
497
            else:
 
498
                # no default to raise if not provided.
 
499
                url = kwargs.get('to_location')
 
500
            return self.bzrdir.clone(url,
 
501
                                     revision_id=revision_id,
 
502
                                     basis=basis).open_branch()
 
503
        # new cleaner api.
 
504
        # generate args by hand 
 
505
        if len(args) > 1:
 
506
            revision_id = args[1]
 
507
        else:
 
508
            revision_id = kwargs.get('revision_id', None)
 
509
        if len(args):
 
510
            to_bzrdir = args[0]
 
511
        else:
 
512
            # no default to raise if not provided.
 
513
            to_bzrdir = kwargs.get('to_bzrdir')
 
514
        result = self._format.initialize(to_bzrdir)
 
515
        self.copy_content_into(result, revision_id=revision_id)
 
516
        return  result
 
517
 
 
518
    @needs_read_lock
 
519
    def sprout(self, to_bzrdir, revision_id=None):
 
520
        """Create a new line of development from the branch, into to_bzrdir.
 
521
        
 
522
        revision_id: if not None, the revision history in the new branch will
 
523
                     be truncated to end with revision_id.
 
524
        """
 
525
        result = self._format.initialize(to_bzrdir)
 
526
        self.copy_content_into(result, revision_id=revision_id)
 
527
        result.set_parent(self.bzrdir.root_transport.base)
 
528
        return result
 
529
 
 
530
    @needs_read_lock
 
531
    def copy_content_into(self, destination, revision_id=None):
 
532
        """Copy the content of self into destination.
 
533
 
 
534
        revision_id: if not None, the revision history in the new branch will
 
535
                     be truncated to end with revision_id.
 
536
        """
 
537
        new_history = self.revision_history()
 
538
        if revision_id is not None:
 
539
            try:
 
540
                new_history = new_history[:new_history.index(revision_id) + 1]
 
541
            except ValueError:
 
542
                rev = self.repository.get_revision(revision_id)
 
543
                new_history = rev.get_history(self.repository)[1:]
 
544
        destination.set_revision_history(new_history)
 
545
        try:
 
546
            parent = self.get_parent()
 
547
        except errors.InaccessibleParent, e:
 
548
            mutter('parent was not accessible to copy: %s', e)
 
549
        else:
 
550
            if parent:
 
551
                destination.set_parent(parent)
 
552
 
 
553
    @needs_read_lock
 
554
    def check(self):
 
555
        """Check consistency of the branch.
 
556
 
 
557
        In particular this checks that revisions given in the revision-history
 
558
        do actually match up in the revision graph, and that they're all 
 
559
        present in the repository.
 
560
        
 
561
        Callers will typically also want to check the repository.
 
562
 
 
563
        :return: A BranchCheckResult.
 
564
        """
 
565
        mainline_parent_id = None
 
566
        for revision_id in self.revision_history():
 
567
            try:
 
568
                revision = self.repository.get_revision(revision_id)
 
569
            except errors.NoSuchRevision, e:
 
570
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
 
571
                            % revision_id)
 
572
            # In general the first entry on the revision history has no parents.
 
573
            # But it's not illegal for it to have parents listed; this can happen
 
574
            # in imports from Arch when the parents weren't reachable.
 
575
            if mainline_parent_id is not None:
 
576
                if mainline_parent_id not in revision.parent_ids:
 
577
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
 
578
                                        "parents of {%s}"
 
579
                                        % (mainline_parent_id, revision_id))
 
580
            mainline_parent_id = revision_id
 
581
        return BranchCheckResult(self)
 
582
 
 
583
    def create_checkout(self, to_location, revision_id=None, 
 
584
                        lightweight=False):
 
585
        """Create a checkout of a branch.
 
586
        
 
587
        :param to_location: The url to produce the checkout at
 
588
        :param revision_id: The revision to check out
 
589
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
590
        produce a bound branch (heavyweight checkout)
 
591
        :return: The tree of the created checkout
 
592
        """
 
593
        if lightweight:
 
594
            t = transport.get_transport(to_location)
 
595
            try:
 
596
                t.mkdir('.')
 
597
            except errors.FileExists:
 
598
                pass
 
599
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
600
            BranchReferenceFormat().initialize(checkout, self)
 
601
        else:
 
602
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
603
                to_location, force_new_tree=False)
 
604
            checkout = checkout_branch.bzrdir
 
605
            checkout_branch.bind(self)
 
606
            if revision_id is not None:
 
607
                rh = checkout_branch.revision_history()
 
608
                new_rh = rh[:rh.index(revision_id) + 1]
 
609
                checkout_branch.set_revision_history(new_rh)
 
610
        return checkout.create_workingtree(revision_id)
 
611
 
 
612
 
 
613
class BranchFormat(object):
 
614
    """An encapsulation of the initialization and open routines for a format.
 
615
 
 
616
    Formats provide three things:
 
617
     * An initialization routine,
 
618
     * a format string,
 
619
     * an open routine.
 
620
 
 
621
    Formats are placed in an dict by their format string for reference 
 
622
    during branch opening. Its not required that these be instances, they
 
623
    can be classes themselves with class methods - it simply depends on 
 
624
    whether state is needed for a given format or not.
 
625
 
 
626
    Once a format is deprecated, just deprecate the initialize and open
 
627
    methods on the format class. Do not deprecate the object, as the 
 
628
    object will be created every time regardless.
 
629
    """
 
630
 
 
631
    _default_format = None
 
632
    """The default format used for new branches."""
 
633
 
 
634
    _formats = {}
 
635
    """The known formats."""
 
636
 
 
637
    @classmethod
 
638
    def find_format(klass, a_bzrdir):
 
639
        """Return the format for the branch object in a_bzrdir."""
 
640
        try:
 
641
            transport = a_bzrdir.get_branch_transport(None)
 
642
            format_string = transport.get("format").read()
 
643
            return klass._formats[format_string]
 
644
        except NoSuchFile:
 
645
            raise NotBranchError(path=transport.base)
 
646
        except KeyError:
 
647
            raise errors.UnknownFormatError(format=format_string)
 
648
 
 
649
    @classmethod
 
650
    def get_default_format(klass):
 
651
        """Return the current default format."""
 
652
        return klass._default_format
 
653
 
 
654
    def get_format_string(self):
 
655
        """Return the ASCII format string that identifies this format."""
 
656
        raise NotImplementedError(self.get_format_string)
 
657
 
 
658
    def get_format_description(self):
 
659
        """Return the short format description for this format."""
 
660
        raise NotImplementedError(self.get_format_string)
 
661
 
 
662
    def initialize(self, a_bzrdir):
 
663
        """Create a branch of this format in a_bzrdir."""
 
664
        raise NotImplementedError(self.initialize)
 
665
 
 
666
    def is_supported(self):
 
667
        """Is this format supported?
 
668
 
 
669
        Supported formats can be initialized and opened.
 
670
        Unsupported formats may not support initialization or committing or 
 
671
        some other features depending on the reason for not being supported.
 
672
        """
 
673
        return True
 
674
 
 
675
    def open(self, a_bzrdir, _found=False):
 
676
        """Return the branch object for a_bzrdir
 
677
 
 
678
        _found is a private parameter, do not use it. It is used to indicate
 
679
               if format probing has already be done.
 
680
        """
 
681
        raise NotImplementedError(self.open)
 
682
 
 
683
    @classmethod
 
684
    def register_format(klass, format):
 
685
        klass._formats[format.get_format_string()] = format
 
686
 
 
687
    @classmethod
 
688
    def set_default_format(klass, format):
 
689
        klass._default_format = format
 
690
 
 
691
    @classmethod
 
692
    def unregister_format(klass, format):
 
693
        assert klass._formats[format.get_format_string()] is format
 
694
        del klass._formats[format.get_format_string()]
 
695
 
 
696
    def __str__(self):
 
697
        return self.get_format_string().rstrip()
 
698
 
 
699
 
 
700
class BzrBranchFormat4(BranchFormat):
 
701
    """Bzr branch format 4.
 
702
 
 
703
    This format has:
 
704
     - a revision-history file.
 
705
     - a branch-lock lock file [ to be shared with the bzrdir ]
 
706
    """
 
707
 
 
708
    def get_format_description(self):
 
709
        """See BranchFormat.get_format_description()."""
 
710
        return "Branch format 4"
 
711
 
 
712
    def initialize(self, a_bzrdir):
 
713
        """Create a branch of this format in a_bzrdir."""
 
714
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
715
        branch_transport = a_bzrdir.get_branch_transport(self)
 
716
        utf8_files = [('revision-history', ''),
 
717
                      ('branch-name', ''),
 
718
                      ]
 
719
        control_files = LockableFiles(branch_transport, 'branch-lock',
 
720
                                      TransportLock)
 
721
        control_files.create_lock()
 
722
        control_files.lock_write()
 
723
        try:
 
724
            for file, content in utf8_files:
 
725
                control_files.put_utf8(file, content)
 
726
        finally:
 
727
            control_files.unlock()
 
728
        return self.open(a_bzrdir, _found=True)
 
729
 
 
730
    def __init__(self):
 
731
        super(BzrBranchFormat4, self).__init__()
 
732
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
733
 
 
734
    def open(self, a_bzrdir, _found=False):
 
735
        """Return the branch object for a_bzrdir
 
736
 
 
737
        _found is a private parameter, do not use it. It is used to indicate
 
738
               if format probing has already be done.
 
739
        """
 
740
        if not _found:
 
741
            # we are being called directly and must probe.
 
742
            raise NotImplementedError
 
743
        return BzrBranch(_format=self,
 
744
                         _control_files=a_bzrdir._control_files,
 
745
                         a_bzrdir=a_bzrdir,
 
746
                         _repository=a_bzrdir.open_repository())
 
747
 
 
748
    def __str__(self):
 
749
        return "Bazaar-NG branch format 4"
 
750
 
 
751
 
 
752
class BzrBranchFormat5(BranchFormat):
 
753
    """Bzr branch format 5.
 
754
 
 
755
    This format has:
 
756
     - a revision-history file.
 
757
     - a format string
 
758
     - a lock dir guarding the branch itself
 
759
     - all of this stored in a branch/ subdirectory
 
760
     - works with shared repositories.
 
761
 
 
762
    This format is new in bzr 0.8.
 
763
    """
 
764
 
 
765
    def get_format_string(self):
 
766
        """See BranchFormat.get_format_string()."""
 
767
        return "Bazaar-NG branch format 5\n"
 
768
 
 
769
    def get_format_description(self):
 
770
        """See BranchFormat.get_format_description()."""
 
771
        return "Branch format 5"
 
772
        
 
773
    def initialize(self, a_bzrdir):
 
774
        """Create a branch of this format in a_bzrdir."""
 
775
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
776
        branch_transport = a_bzrdir.get_branch_transport(self)
 
777
        utf8_files = [('revision-history', ''),
 
778
                      ('branch-name', ''),
 
779
                      ]
 
780
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
 
781
        control_files.create_lock()
 
782
        control_files.lock_write()
 
783
        control_files.put_utf8('format', self.get_format_string())
 
784
        try:
 
785
            for file, content in utf8_files:
 
786
                control_files.put_utf8(file, content)
 
787
        finally:
 
788
            control_files.unlock()
 
789
        return self.open(a_bzrdir, _found=True, )
 
790
 
 
791
    def __init__(self):
 
792
        super(BzrBranchFormat5, self).__init__()
 
793
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
794
 
 
795
    def open(self, a_bzrdir, _found=False):
 
796
        """Return the branch object for a_bzrdir
 
797
 
 
798
        _found is a private parameter, do not use it. It is used to indicate
 
799
               if format probing has already be done.
 
800
        """
 
801
        if not _found:
 
802
            format = BranchFormat.find_format(a_bzrdir)
 
803
            assert format.__class__ == self.__class__
 
804
        transport = a_bzrdir.get_branch_transport(None)
 
805
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
 
806
        return BzrBranch5(_format=self,
 
807
                          _control_files=control_files,
 
808
                          a_bzrdir=a_bzrdir,
 
809
                          _repository=a_bzrdir.find_repository())
 
810
 
 
811
    def __str__(self):
 
812
        return "Bazaar-NG Metadir branch format 5"
 
813
 
 
814
 
 
815
class BranchReferenceFormat(BranchFormat):
 
816
    """Bzr branch reference format.
 
817
 
 
818
    Branch references are used in implementing checkouts, they
 
819
    act as an alias to the real branch which is at some other url.
 
820
 
 
821
    This format has:
 
822
     - A location file
 
823
     - a format string
 
824
    """
 
825
 
 
826
    def get_format_string(self):
 
827
        """See BranchFormat.get_format_string()."""
 
828
        return "Bazaar-NG Branch Reference Format 1\n"
 
829
 
 
830
    def get_format_description(self):
 
831
        """See BranchFormat.get_format_description()."""
 
832
        return "Checkout reference format 1"
 
833
        
 
834
    def initialize(self, a_bzrdir, target_branch=None):
 
835
        """Create a branch of this format in a_bzrdir."""
 
836
        if target_branch is None:
 
837
            # this format does not implement branch itself, thus the implicit
 
838
            # creation contract must see it as uninitializable
 
839
            raise errors.UninitializableFormat(self)
 
840
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
841
        branch_transport = a_bzrdir.get_branch_transport(self)
 
842
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
843
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
844
        branch_transport.put('format', StringIO(self.get_format_string()))
 
845
        return self.open(a_bzrdir, _found=True)
 
846
 
 
847
    def __init__(self):
 
848
        super(BranchReferenceFormat, self).__init__()
 
849
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
850
 
 
851
    def _make_reference_clone_function(format, a_branch):
 
852
        """Create a clone() routine for a branch dynamically."""
 
853
        def clone(to_bzrdir, revision_id=None):
 
854
            """See Branch.clone()."""
 
855
            return format.initialize(to_bzrdir, a_branch)
 
856
            # cannot obey revision_id limits when cloning a reference ...
 
857
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
858
            # emit some sort of warning/error to the caller ?!
 
859
        return clone
 
860
 
 
861
    def open(self, a_bzrdir, _found=False):
 
862
        """Return the branch that the branch reference in a_bzrdir points at.
 
863
 
 
864
        _found is a private parameter, do not use it. It is used to indicate
 
865
               if format probing has already be done.
 
866
        """
 
867
        if not _found:
 
868
            format = BranchFormat.find_format(a_bzrdir)
 
869
            assert format.__class__ == self.__class__
 
870
        transport = a_bzrdir.get_branch_transport(None)
 
871
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
872
        result = real_bzrdir.open_branch()
 
873
        # this changes the behaviour of result.clone to create a new reference
 
874
        # rather than a copy of the content of the branch.
 
875
        # I did not use a proxy object because that needs much more extensive
 
876
        # testing, and we are only changing one behaviour at the moment.
 
877
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
878
        # then this should be refactored to introduce a tested proxy branch
 
879
        # and a subclass of that for use in overriding clone() and ....
 
880
        # - RBC 20060210
 
881
        result.clone = self._make_reference_clone_function(result)
 
882
        return result
 
883
 
 
884
 
 
885
# formats which have no format string are not discoverable
 
886
# and not independently creatable, so are not registered.
 
887
__default_format = BzrBranchFormat5()
 
888
BranchFormat.register_format(__default_format)
 
889
BranchFormat.register_format(BranchReferenceFormat())
 
890
BranchFormat.set_default_format(__default_format)
 
891
_legacy_formats = [BzrBranchFormat4(),
 
892
                   ]
 
893
 
 
894
class BzrBranch(Branch):
 
895
    """A branch stored in the actual filesystem.
 
896
 
 
897
    Note that it's "local" in the context of the filesystem; it doesn't
 
898
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
899
    it's writable, and can be accessed via the normal filesystem API.
 
900
    """
 
901
    
 
902
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
 
903
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
904
                 _control_files=None, a_bzrdir=None, _repository=None):
 
905
        """Create new branch object at a particular location.
 
906
 
 
907
        transport -- A Transport object, defining how to access files.
 
908
        
 
909
        init -- If True, create new control files in a previously
 
910
             unversioned directory.  If False, the branch must already
 
911
             be versioned.
 
912
 
 
913
        relax_version_check -- If true, the usual check for the branch
 
914
            version is not applied.  This is intended only for
 
915
            upgrade/recovery type use; it's not guaranteed that
 
916
            all operations will work on old format branches.
 
917
        """
 
918
        if a_bzrdir is None:
 
919
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
920
        else:
 
921
            self.bzrdir = a_bzrdir
 
922
        self._transport = self.bzrdir.transport.clone('..')
 
923
        self._base = self._transport.base
 
924
        self._format = _format
 
925
        if _control_files is None:
 
926
            raise ValueError('BzrBranch _control_files is None')
 
927
        self.control_files = _control_files
 
928
        if deprecated_passed(init):
 
929
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
930
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
931
                 DeprecationWarning,
 
932
                 stacklevel=2)
 
933
            if init:
 
934
                # this is slower than before deprecation, oh well never mind.
 
935
                # -> its deprecated.
 
936
                self._initialize(transport.base)
 
937
        self._check_format(_format)
 
938
        if deprecated_passed(relax_version_check):
 
939
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
940
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
941
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
 
942
                 "open() method.",
 
943
                 DeprecationWarning,
 
944
                 stacklevel=2)
 
945
            if (not relax_version_check
 
946
                and not self._format.is_supported()):
 
947
                raise errors.UnsupportedFormatError(format=fmt)
 
948
        if deprecated_passed(transport):
 
949
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
950
                 "parameter is deprecated as of bzr 0.8. "
 
951
                 "Please use Branch.open, or bzrdir.open_branch().",
 
952
                 DeprecationWarning,
 
953
                 stacklevel=2)
 
954
        self.repository = _repository
 
955
 
 
956
    def __str__(self):
 
957
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
958
 
 
959
    __repr__ = __str__
1326
960
 
1327
961
    def __del__(self):
1328
 
        self.destroy()
1329
 
 
1330
 
    def destroy(self):
1331
 
        """Destroy the test branch, removing the scratch directory."""
1332
 
        from shutil import rmtree
1333
 
        try:
1334
 
            if self.base:
1335
 
                mutter("delete ScratchBranch %s" % self.base)
1336
 
                rmtree(self.base)
1337
 
        except OSError, e:
1338
 
            # Work around for shutil.rmtree failing on Windows when
1339
 
            # readonly files are encountered
1340
 
            mutter("hit exception in destroying ScratchBranch: %s" % e)
1341
 
            for root, dirs, files in os.walk(self.base, topdown=False):
1342
 
                for name in files:
1343
 
                    os.chmod(os.path.join(root, name), 0700)
1344
 
            rmtree(self.base)
1345
 
        self._transport = None
1346
 
 
 
962
        # TODO: It might be best to do this somewhere else,
 
963
        # but it is nice for a Branch object to automatically
 
964
        # cache it's information.
 
965
        # Alternatively, we could have the Transport objects cache requests
 
966
        # See the earlier discussion about how major objects (like Branch)
 
967
        # should never expect their __del__ function to run.
 
968
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
 
969
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
970
            try:
 
971
                osutils.rmtree(self.cache_root)
 
972
            except:
 
973
                pass
 
974
            self.cache_root = None
 
975
 
 
976
    def _get_base(self):
 
977
        return self._base
 
978
 
 
979
    base = property(_get_base, doc="The URL for the root of this branch.")
 
980
 
 
981
    def _finish_transaction(self):
 
982
        """Exit the current transaction."""
 
983
        return self.control_files._finish_transaction()
 
984
 
 
985
    def get_transaction(self):
 
986
        """Return the current active transaction.
 
987
 
 
988
        If no transaction is active, this returns a passthrough object
 
989
        for which all data is immediately flushed and no caching happens.
 
990
        """
 
991
        # this is an explicit function so that we can do tricky stuff
 
992
        # when the storage in rev_storage is elsewhere.
 
993
        # we probably need to hook the two 'lock a location' and 
 
994
        # 'have a transaction' together more delicately, so that
 
995
        # we can have two locks (branch and storage) and one transaction
 
996
        # ... and finishing the transaction unlocks both, but unlocking
 
997
        # does not. - RBC 20051121
 
998
        return self.control_files.get_transaction()
 
999
 
 
1000
    def _set_transaction(self, transaction):
 
1001
        """Set a new active transaction."""
 
1002
        return self.control_files._set_transaction(transaction)
 
1003
 
 
1004
    def abspath(self, name):
 
1005
        """See Branch.abspath."""
 
1006
        return self.control_files._transport.abspath(name)
 
1007
 
 
1008
    def _check_format(self, format):
 
1009
        """Identify the branch format if needed.
 
1010
 
 
1011
        The format is stored as a reference to the format object in
 
1012
        self._format for code that needs to check it later.
 
1013
 
 
1014
        The format parameter is either None or the branch format class
 
1015
        used to open this branch.
 
1016
 
 
1017
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
 
1018
        """
 
1019
        if format is None:
 
1020
            format = BranchFormat.find_format(self.bzrdir)
 
1021
        self._format = format
 
1022
        mutter("got branch format %s", self._format)
 
1023
 
 
1024
    @needs_read_lock
 
1025
    def get_root_id(self):
 
1026
        """See Branch.get_root_id."""
 
1027
        tree = self.repository.revision_tree(self.last_revision())
 
1028
        return tree.inventory.root.file_id
 
1029
 
 
1030
    def is_locked(self):
 
1031
        return self.control_files.is_locked()
 
1032
 
 
1033
    def lock_write(self):
 
1034
        self.repository.lock_write()
 
1035
        try:
 
1036
            self.control_files.lock_write()
 
1037
        except:
 
1038
            self.repository.unlock()
 
1039
            raise
 
1040
 
 
1041
    def lock_read(self):
 
1042
        self.repository.lock_read()
 
1043
        try:
 
1044
            self.control_files.lock_read()
 
1045
        except:
 
1046
            self.repository.unlock()
 
1047
            raise
 
1048
 
 
1049
    def unlock(self):
 
1050
        # TODO: test for failed two phase locks. This is known broken.
 
1051
        try:
 
1052
            self.control_files.unlock()
 
1053
        finally:
 
1054
            self.repository.unlock()
 
1055
        
 
1056
    def peek_lock_mode(self):
 
1057
        if self.control_files._lock_count == 0:
 
1058
            return None
 
1059
        else:
 
1060
            return self.control_files._lock_mode
 
1061
 
 
1062
    def get_physical_lock_status(self):
 
1063
        return self.control_files.get_physical_lock_status()
 
1064
 
 
1065
    @needs_read_lock
 
1066
    def print_file(self, file, revision_id):
 
1067
        """See Branch.print_file."""
 
1068
        return self.repository.print_file(file, revision_id)
 
1069
 
 
1070
    @needs_write_lock
 
1071
    def append_revision(self, *revision_ids):
 
1072
        """See Branch.append_revision."""
 
1073
        for revision_id in revision_ids:
 
1074
            mutter("add {%s} to revision-history" % revision_id)
 
1075
        rev_history = self.revision_history()
 
1076
        rev_history.extend(revision_ids)
 
1077
        self.set_revision_history(rev_history)
 
1078
 
 
1079
    @needs_write_lock
 
1080
    def set_revision_history(self, rev_history):
 
1081
        """See Branch.set_revision_history."""
 
1082
        self.control_files.put_utf8(
 
1083
            'revision-history', '\n'.join(rev_history))
 
1084
        transaction = self.get_transaction()
 
1085
        history = transaction.map.find_revision_history()
 
1086
        if history is not None:
 
1087
            # update the revision history in the identity map.
 
1088
            history[:] = list(rev_history)
 
1089
            # this call is disabled because revision_history is 
 
1090
            # not really an object yet, and the transaction is for objects.
 
1091
            # transaction.register_dirty(history)
 
1092
        else:
 
1093
            transaction.map.add_revision_history(rev_history)
 
1094
            # this call is disabled because revision_history is 
 
1095
            # not really an object yet, and the transaction is for objects.
 
1096
            # transaction.register_clean(history)
 
1097
 
 
1098
    @needs_read_lock
 
1099
    def revision_history(self):
 
1100
        """See Branch.revision_history."""
 
1101
        transaction = self.get_transaction()
 
1102
        history = transaction.map.find_revision_history()
 
1103
        if history is not None:
 
1104
            # mutter("cache hit for revision-history in %s", self)
 
1105
            return list(history)
 
1106
        decode_utf8 = cache_utf8.decode
 
1107
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1108
                self.control_files.get('revision-history').readlines()]
 
1109
        transaction.map.add_revision_history(history)
 
1110
        # this call is disabled because revision_history is 
 
1111
        # not really an object yet, and the transaction is for objects.
 
1112
        # transaction.register_clean(history, precious=True)
 
1113
        return list(history)
 
1114
 
 
1115
    @needs_write_lock
 
1116
    def generate_revision_history(self, revision_id, last_rev=None, 
 
1117
        other_branch=None):
 
1118
        """Create a new revision history that will finish with revision_id.
 
1119
        
 
1120
        :param revision_id: the new tip to use.
 
1121
        :param last_rev: The previous last_revision. If not None, then this
 
1122
            must be a ancestory of revision_id, or DivergedBranches is raised.
 
1123
        :param other_branch: The other branch that DivergedBranches should
 
1124
            raise with respect to.
 
1125
        """
 
1126
        # stop_revision must be a descendant of last_revision
 
1127
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1128
        if last_rev is not None and last_rev not in stop_graph:
 
1129
            # our previous tip is not merged into stop_revision
 
1130
            raise errors.DivergedBranches(self, other_branch)
 
1131
        # make a new revision history from the graph
 
1132
        current_rev_id = revision_id
 
1133
        new_history = []
 
1134
        while current_rev_id not in (None, revision.NULL_REVISION):
 
1135
            new_history.append(current_rev_id)
 
1136
            current_rev_id_parents = stop_graph[current_rev_id]
 
1137
            try:
 
1138
                current_rev_id = current_rev_id_parents[0]
 
1139
            except IndexError:
 
1140
                current_rev_id = None
 
1141
        new_history.reverse()
 
1142
        self.set_revision_history(new_history)
 
1143
 
 
1144
    @needs_write_lock
 
1145
    def update_revisions(self, other, stop_revision=None):
 
1146
        """See Branch.update_revisions."""
 
1147
        other.lock_read()
 
1148
        try:
 
1149
            if stop_revision is None:
 
1150
                stop_revision = other.last_revision()
 
1151
                if stop_revision is None:
 
1152
                    # if there are no commits, we're done.
 
1153
                    return
 
1154
            # whats the current last revision, before we fetch [and change it
 
1155
            # possibly]
 
1156
            last_rev = self.last_revision()
 
1157
            # we fetch here regardless of whether we need to so that we pickup
 
1158
            # filled in ghosts.
 
1159
            self.fetch(other, stop_revision)
 
1160
            my_ancestry = self.repository.get_ancestry(last_rev)
 
1161
            if stop_revision in my_ancestry:
 
1162
                # last_revision is a descendant of stop_revision
 
1163
                return
 
1164
            self.generate_revision_history(stop_revision, last_rev=last_rev,
 
1165
                other_branch=other)
 
1166
        finally:
 
1167
            other.unlock()
 
1168
 
 
1169
    def basis_tree(self):
 
1170
        """See Branch.basis_tree."""
 
1171
        return self.repository.revision_tree(self.last_revision())
 
1172
 
 
1173
    @deprecated_method(zero_eight)
 
1174
    def working_tree(self):
 
1175
        """Create a Working tree object for this branch."""
 
1176
 
 
1177
        from bzrlib.transport.local import LocalTransport
 
1178
        if (self.base.find('://') != -1 or 
 
1179
            not isinstance(self._transport, LocalTransport)):
 
1180
            raise NoWorkingTree(self.base)
 
1181
        return self.bzrdir.open_workingtree()
 
1182
 
 
1183
    @needs_write_lock
 
1184
    def pull(self, source, overwrite=False, stop_revision=None):
 
1185
        """See Branch.pull."""
 
1186
        source.lock_read()
 
1187
        try:
 
1188
            old_count = len(self.revision_history())
 
1189
            try:
 
1190
                self.update_revisions(source,stop_revision)
 
1191
            except DivergedBranches:
 
1192
                if not overwrite:
 
1193
                    raise
 
1194
            if overwrite:
 
1195
                self.set_revision_history(source.revision_history())
 
1196
            new_count = len(self.revision_history())
 
1197
            return new_count - old_count
 
1198
        finally:
 
1199
            source.unlock()
 
1200
 
 
1201
    def get_parent(self):
 
1202
        """See Branch.get_parent."""
 
1203
 
 
1204
        _locs = ['parent', 'pull', 'x-pull']
 
1205
        assert self.base[-1] == '/'
 
1206
        for l in _locs:
 
1207
            try:
 
1208
                parent = self.control_files.get(l).read().strip('\n')
 
1209
            except NoSuchFile:
 
1210
                continue
 
1211
            # This is an old-format absolute path to a local branch
 
1212
            # turn it into a url
 
1213
            if parent.startswith('/'):
 
1214
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1215
            try:
 
1216
                return urlutils.join(self.base[:-1], parent)
 
1217
            except errors.InvalidURLJoin, e:
 
1218
                raise errors.InaccessibleParent(parent, self.base)
 
1219
        return None
 
1220
 
 
1221
    def get_push_location(self):
 
1222
        """See Branch.get_push_location."""
 
1223
        push_loc = self.get_config().get_user_option('push_location')
 
1224
        return push_loc
 
1225
 
 
1226
    def set_push_location(self, location):
 
1227
        """See Branch.set_push_location."""
 
1228
        self.get_config().set_user_option('push_location', location, 
 
1229
                                          local=True)
 
1230
 
 
1231
    @needs_write_lock
 
1232
    def set_parent(self, url):
 
1233
        """See Branch.set_parent."""
 
1234
        # TODO: Maybe delete old location files?
 
1235
        # URLs should never be unicode, even on the local fs,
 
1236
        # FIXUP this and get_parent in a future branch format bump:
 
1237
        # read and rewrite the file, and have the new format code read
 
1238
        # using .get not .get_utf8. RBC 20060125
 
1239
        if url is None:
 
1240
            self.control_files._transport.delete('parent')
 
1241
        else:
 
1242
            if isinstance(url, unicode):
 
1243
                try: 
 
1244
                    url = url.encode('ascii')
 
1245
                except UnicodeEncodeError:
 
1246
                    raise bzrlib.errors.InvalidURL(url,
 
1247
                        "Urls must be 7-bit ascii, "
 
1248
                        "use bzrlib.urlutils.escape")
 
1249
                    
 
1250
            url = urlutils.relative_url(self.base, url)
 
1251
            self.control_files.put('parent', url + '\n')
 
1252
 
 
1253
    @deprecated_function(zero_nine)
 
1254
    def tree_config(self):
 
1255
        """DEPRECATED; call get_config instead.  
 
1256
        TreeConfig has become part of BranchConfig."""
 
1257
        return TreeConfig(self)
 
1258
 
 
1259
 
 
1260
class BzrBranch5(BzrBranch):
 
1261
    """A format 5 branch. This supports new features over plan branches.
 
1262
 
 
1263
    It has support for a master_branch which is the data for bound branches.
 
1264
    """
 
1265
 
 
1266
    def __init__(self,
 
1267
                 _format,
 
1268
                 _control_files,
 
1269
                 a_bzrdir,
 
1270
                 _repository):
 
1271
        super(BzrBranch5, self).__init__(_format=_format,
 
1272
                                         _control_files=_control_files,
 
1273
                                         a_bzrdir=a_bzrdir,
 
1274
                                         _repository=_repository)
 
1275
        
 
1276
    @needs_write_lock
 
1277
    def pull(self, source, overwrite=False, stop_revision=None):
 
1278
        """Updates branch.pull to be bound branch aware."""
 
1279
        bound_location = self.get_bound_location()
 
1280
        if source.base != bound_location:
 
1281
            # not pulling from master, so we need to update master.
 
1282
            master_branch = self.get_master_branch()
 
1283
            if master_branch:
 
1284
                master_branch.pull(source)
 
1285
                source = master_branch
 
1286
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
1287
 
 
1288
    def get_bound_location(self):
 
1289
        try:
 
1290
            return self.control_files.get_utf8('bound').read()[:-1]
 
1291
        except errors.NoSuchFile:
 
1292
            return None
 
1293
 
 
1294
    @needs_read_lock
 
1295
    def get_master_branch(self):
 
1296
        """Return the branch we are bound to.
 
1297
        
 
1298
        :return: Either a Branch, or None
 
1299
 
 
1300
        This could memoise the branch, but if thats done
 
1301
        it must be revalidated on each new lock.
 
1302
        So for now we just don't memoise it.
 
1303
        # RBC 20060304 review this decision.
 
1304
        """
 
1305
        bound_loc = self.get_bound_location()
 
1306
        if not bound_loc:
 
1307
            return None
 
1308
        try:
 
1309
            return Branch.open(bound_loc)
 
1310
        except (errors.NotBranchError, errors.ConnectionError), e:
 
1311
            raise errors.BoundBranchConnectionFailure(
 
1312
                    self, bound_loc, e)
 
1313
 
 
1314
    @needs_write_lock
 
1315
    def set_bound_location(self, location):
 
1316
        """Set the target where this branch is bound to.
 
1317
 
 
1318
        :param location: URL to the target branch
 
1319
        """
 
1320
        if location:
 
1321
            self.control_files.put_utf8('bound', location+'\n')
 
1322
        else:
 
1323
            try:
 
1324
                self.control_files._transport.delete('bound')
 
1325
            except NoSuchFile:
 
1326
                return False
 
1327
            return True
 
1328
 
 
1329
    @needs_write_lock
 
1330
    def bind(self, other):
 
1331
        """Bind the local branch the other branch.
 
1332
 
 
1333
        :param other: The branch to bind to
 
1334
        :type other: Branch
 
1335
        """
 
1336
        # TODO: jam 20051230 Consider checking if the target is bound
 
1337
        #       It is debatable whether you should be able to bind to
 
1338
        #       a branch which is itself bound.
 
1339
        #       Committing is obviously forbidden,
 
1340
        #       but binding itself may not be.
 
1341
        #       Since we *have* to check at commit time, we don't
 
1342
        #       *need* to check here
 
1343
        self.pull(other)
 
1344
 
 
1345
        # we are now equal to or a suffix of other.
 
1346
 
 
1347
        # Since we have 'pulled' from the remote location,
 
1348
        # now we should try to pull in the opposite direction
 
1349
        # in case the local tree has more revisions than the
 
1350
        # remote one.
 
1351
        # There may be a different check you could do here
 
1352
        # rather than actually trying to install revisions remotely.
 
1353
        # TODO: capture an exception which indicates the remote branch
 
1354
        #       is not writable. 
 
1355
        #       If it is up-to-date, this probably should not be a failure
 
1356
        
 
1357
        # lock other for write so the revision-history syncing cannot race
 
1358
        other.lock_write()
 
1359
        try:
 
1360
            other.pull(self)
 
1361
            # if this does not error, other now has the same last rev we do
 
1362
            # it can only error if the pull from other was concurrent with
 
1363
            # a commit to other from someone else.
 
1364
 
 
1365
            # until we ditch revision-history, we need to sync them up:
 
1366
            self.set_revision_history(other.revision_history())
 
1367
            # now other and self are up to date with each other and have the
 
1368
            # same revision-history.
 
1369
        finally:
 
1370
            other.unlock()
 
1371
 
 
1372
        self.set_bound_location(other.base)
 
1373
 
 
1374
    @needs_write_lock
 
1375
    def unbind(self):
 
1376
        """If bound, unbind"""
 
1377
        return self.set_bound_location(None)
 
1378
 
 
1379
    @needs_write_lock
 
1380
    def update(self):
 
1381
        """Synchronise this branch with the master branch if any. 
 
1382
 
 
1383
        :return: None or the last_revision that was pivoted out during the
 
1384
                 update.
 
1385
        """
 
1386
        master = self.get_master_branch()
 
1387
        if master is not None:
 
1388
            old_tip = self.last_revision()
 
1389
            self.pull(master, overwrite=True)
 
1390
            if old_tip in self.repository.get_ancestry(self.last_revision()):
 
1391
                return None
 
1392
            return old_tip
 
1393
        return None
 
1394
 
 
1395
 
 
1396
class BranchTestProviderAdapter(object):
 
1397
    """A tool to generate a suite testing multiple branch formats at once.
 
1398
 
 
1399
    This is done by copying the test once for each transport and injecting
 
1400
    the transport_server, transport_readonly_server, and branch_format
 
1401
    classes into each copy. Each copy is also given a new id() to make it
 
1402
    easy to identify.
 
1403
    """
 
1404
 
 
1405
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1406
        self._transport_server = transport_server
 
1407
        self._transport_readonly_server = transport_readonly_server
 
1408
        self._formats = formats
1347
1409
    
 
1410
    def adapt(self, test):
 
1411
        result = TestSuite()
 
1412
        for branch_format, bzrdir_format in self._formats:
 
1413
            new_test = deepcopy(test)
 
1414
            new_test.transport_server = self._transport_server
 
1415
            new_test.transport_readonly_server = self._transport_readonly_server
 
1416
            new_test.bzrdir_format = bzrdir_format
 
1417
            new_test.branch_format = branch_format
 
1418
            def make_new_test_id():
 
1419
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
1420
                return lambda: new_id
 
1421
            new_test.id = make_new_test_id()
 
1422
            result.addTest(new_test)
 
1423
        return result
 
1424
 
 
1425
 
 
1426
class BranchCheckResult(object):
 
1427
    """Results of checking branch consistency.
 
1428
 
 
1429
    :see: Branch.check
 
1430
    """
 
1431
 
 
1432
    def __init__(self, branch):
 
1433
        self.branch = branch
 
1434
 
 
1435
    def report_results(self, verbose):
 
1436
        """Report the check results via trace.note.
 
1437
        
 
1438
        :param verbose: Requests more detailed display of what was checked,
 
1439
            if any.
 
1440
        """
 
1441
        note('checked branch %s format %s',
 
1442
             self.branch.base,
 
1443
             self.branch._format)
 
1444
 
1348
1445
 
1349
1446
######################################################################
1350
1447
# predicates
1351
1448
 
1352
1449
 
1353
 
def is_control_file(filename):
1354
 
    ## FIXME: better check
1355
 
    filename = os.path.normpath(filename)
1356
 
    while filename != '':
1357
 
        head, tail = os.path.split(filename)
1358
 
        ## mutter('check %r for control file' % ((head, tail), ))
1359
 
        if tail == bzrlib.BZRDIR:
1360
 
            return True
1361
 
        if filename == head:
1362
 
            break
1363
 
        filename = head
1364
 
    return False
1365
 
 
1366
 
 
1367
 
 
1368
 
def gen_file_id(name):
1369
 
    """Return new file id.
1370
 
 
1371
 
    This should probably generate proper UUIDs, but for the moment we
1372
 
    cope with just randomness because running uuidgen every time is
1373
 
    slow."""
1374
 
    import re
1375
 
    from binascii import hexlify
1376
 
    from time import time
1377
 
 
1378
 
    # get last component
1379
 
    idx = name.rfind('/')
1380
 
    if idx != -1:
1381
 
        name = name[idx+1 : ]
1382
 
    idx = name.rfind('\\')
1383
 
    if idx != -1:
1384
 
        name = name[idx+1 : ]
1385
 
 
1386
 
    # make it not a hidden file
1387
 
    name = name.lstrip('.')
1388
 
 
1389
 
    # remove any wierd characters; we don't escape them but rather
1390
 
    # just pull them out
1391
 
    name = re.sub(r'[^\w.]', '', name)
1392
 
 
1393
 
    s = hexlify(rand_bytes(8))
1394
 
    return '-'.join((name, compact_date(time()), s))
1395
 
 
1396
 
 
1397
 
def gen_root_id():
1398
 
    """Return a new tree-root file id."""
1399
 
    return gen_file_id('TREE_ROOT')
1400
 
 
1401
 
 
 
1450
@deprecated_function(zero_eight)
 
1451
def is_control_file(*args, **kwargs):
 
1452
    """See bzrlib.workingtree.is_control_file."""
 
1453
    return bzrlib.workingtree.is_control_file(*args, **kwargs)