~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-10-10 03:24:15 UTC
  • Revision ID: robertc@robertcollins.net-20051010032415-2d3840758bc6e9b8
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 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
 
21
from warnings import warn
18
22
from cStringIO import StringIO
19
23
 
20
 
from bzrlib.lazy_import import lazy_import
21
 
lazy_import(globals(), """
22
 
from copy import deepcopy
23
 
from unittest import TestSuite
24
 
from warnings import warn
25
24
 
26
25
import bzrlib
27
 
from bzrlib import (
28
 
        bzrdir,
29
 
        cache_utf8,
30
 
        config as _mod_config,
31
 
        errors,
32
 
        lockdir,
33
 
        lockable_files,
34
 
        osutils,
35
 
        revision as _mod_revision,
36
 
        transport,
37
 
        tree,
38
 
        ui,
39
 
        urlutils,
40
 
        )
41
 
from bzrlib.config import BranchConfig, TreeConfig
42
 
from bzrlib.lockable_files import LockableFiles, TransportLock
43
 
from bzrlib.tag import (
44
 
    BasicTags,
45
 
    DisabledTags,
46
 
    )
47
 
""")
48
 
 
49
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
51
 
                           HistoryMissing, InvalidRevisionId,
52
 
                           InvalidRevisionNumber, LockError, NoSuchFile,
53
 
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
54
 
                           NotBranchError, UninitializableFormat,
55
 
                           UnlistableStore, UnlistableBranch,
56
 
                           )
57
 
from bzrlib.hooks import Hooks
58
 
from bzrlib.symbol_versioning import (deprecated_function,
59
 
                                      deprecated_method,
60
 
                                      DEPRECATED_PARAMETER,
61
 
                                      deprecated_passed,
62
 
                                      zero_eight, zero_nine, zero_sixteen,
63
 
                                      )
 
26
from bzrlib.inventory import InventoryEntry
 
27
import bzrlib.inventory as inventory
64
28
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
import bzrlib.errors as errors
 
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
35
                           DivergedBranches, LockError, UnlistableStore,
 
36
                           UnlistableBranch, NoSuchFile)
 
37
from bzrlib.textui import show_status
 
38
from bzrlib.revision import Revision
 
39
from bzrlib.delta import compare_trees
 
40
from bzrlib.tree import EmptyTree, RevisionTree
 
41
from bzrlib.inventory import Inventory
 
42
from bzrlib.store import copy_all
 
43
from bzrlib.store.compressed_text import CompressedTextStore
 
44
from bzrlib.store.text import TextStore
 
45
from bzrlib.store.weave import WeaveStore
 
46
import bzrlib.transactions as transactions
 
47
from bzrlib.transport import Transport, get_transport
 
48
import bzrlib.xml5
 
49
import bzrlib.ui
65
50
 
66
51
 
67
52
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
68
53
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
69
 
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
70
 
 
71
 
 
72
 
# TODO: Maybe include checks for common corruption of newlines, etc?
 
54
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
55
## TODO: Maybe include checks for common corruption of newlines, etc?
 
56
 
73
57
 
74
58
# TODO: Some operations like log might retrieve the same revisions
75
59
# repeatedly to calculate deltas.  We could perhaps have a weakref
76
60
# cache in memory to make this faster.  In general anything can be
77
 
# cached in memory between lock and unlock operations. .. nb thats
78
 
# what the transaction identity map provides
 
61
# cached in memory between lock and unlock operations.
 
62
 
 
63
def find_branch(*ignored, **ignored_too):
 
64
    # XXX: leave this here for about one release, then remove it
 
65
    raise NotImplementedError('find_branch() is not supported anymore, '
 
66
                              'please use one of the new branch constructors')
 
67
def _relpath(base, path):
 
68
    """Return path relative to base, or raise exception.
 
69
 
 
70
    The path may be either an absolute path or a path relative to the
 
71
    current working directory.
 
72
 
 
73
    Lifted out of Branch.relpath for ease of testing.
 
74
 
 
75
    os.path.commonprefix (python2.4) has a bad bug that it works just
 
76
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
 
77
    avoids that problem."""
 
78
    rp = os.path.abspath(path)
 
79
 
 
80
    s = []
 
81
    head = rp
 
82
    while len(head) >= len(base):
 
83
        if head == base:
 
84
            break
 
85
        head, tail = os.path.split(head)
 
86
        if tail:
 
87
            s.insert(0, tail)
 
88
    else:
 
89
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
 
90
 
 
91
    return os.sep.join(s)
 
92
        
 
93
 
 
94
def find_branch_root(t):
 
95
    """Find the branch root enclosing the transport's base.
 
96
 
 
97
    t is a Transport object.
 
98
 
 
99
    It is not necessary that the base of t exists.
 
100
 
 
101
    Basically we keep looking up until we find the control directory or
 
102
    run into the root.  If there isn't one, raises NotBranchError.
 
103
    """
 
104
    orig_base = t.base
 
105
    while True:
 
106
        if t.has(bzrlib.BZRDIR):
 
107
            return t
 
108
        new_t = t.clone('..')
 
109
        if new_t.base == t.base:
 
110
            # reached the root, whatever that may be
 
111
            raise NotBranchError('%s is not in a branch' % orig_base)
 
112
        t = new_t
79
113
 
80
114
 
81
115
######################################################################
86
120
 
87
121
    base
88
122
        Base directory/url of the branch.
89
 
 
90
 
    hooks: An instance of BranchHooks.
91
123
    """
92
 
    # this is really an instance variable - FIXME move it there
93
 
    # - RBC 20060112
94
124
    base = None
95
125
 
96
 
    # override this to set the strategy for storing tags
97
 
    def _make_tags(self):
98
 
        return DisabledTags(self)
99
 
 
100
126
    def __init__(self, *ignored, **ignored_too):
101
 
        self.tags = self._make_tags()
102
 
        self._revision_history_cache = None
103
 
 
104
 
    def break_lock(self):
105
 
        """Break a lock if one is present from another instance.
106
 
 
107
 
        Uses the ui factory to ask for confirmation if the lock may be from
108
 
        an active process.
109
 
 
110
 
        This will probe the repository for its lock as well.
111
 
        """
112
 
        self.control_files.break_lock()
113
 
        self.repository.break_lock()
114
 
        master = self.get_master_branch()
115
 
        if master is not None:
116
 
            master.break_lock()
 
127
        raise NotImplementedError('The Branch class is abstract')
117
128
 
118
129
    @staticmethod
119
 
    @deprecated_method(zero_eight)
120
130
    def open_downlevel(base):
121
 
        """Open a branch which may be of an old format."""
122
 
        return Branch.open(base, _unsupported=True)
 
131
        """Open a branch which may be of an old format.
 
132
        
 
133
        Only local branches are supported."""
 
134
        return _Branch(get_transport(base), relax_version_check=True)
123
135
        
124
136
    @staticmethod
125
 
    def open(base, _unsupported=False):
126
 
        """Open the branch rooted at base.
127
 
 
128
 
        For instance, if the branch is at URL/.bzr/branch,
129
 
        Branch.open(URL) -> a Branch instance.
130
 
        """
131
 
        control = bzrdir.BzrDir.open(base, _unsupported)
132
 
        return control.open_branch(_unsupported)
 
137
    def open(base):
 
138
        """Open an existing branch, rooted at 'base' (url)"""
 
139
        t = get_transport(base)
 
140
        mutter("trying to open %r with transport %r", base, t)
 
141
        return _Branch(t)
133
142
 
134
143
    @staticmethod
135
144
    def open_containing(url):
136
145
        """Open an existing branch which contains url.
137
146
        
138
147
        This probes for a branch at url, and searches upwards from there.
139
 
 
140
 
        Basically we keep looking up until we find the control directory or
141
 
        run into the root.  If there isn't one, raises NotBranchError.
142
 
        If there is one and it is either an unrecognised format or an unsupported 
143
 
        format, UnknownFormatError or UnsupportedFormatError are raised.
144
 
        If there is one, it is returned, along with the unused portion of url.
145
148
        """
146
 
        control, relpath = bzrdir.BzrDir.open_containing(url)
147
 
        return control.open_branch(), relpath
 
149
        t = get_transport(url)
 
150
        t = find_branch_root(t)
 
151
        return _Branch(t)
148
152
 
149
153
    @staticmethod
150
 
    @deprecated_function(zero_eight)
151
154
    def initialize(base):
152
 
        """Create a new working tree and branch, rooted at 'base' (url)
153
 
 
154
 
        NOTE: This will soon be deprecated in favour of creation
155
 
        through a BzrDir.
156
 
        """
157
 
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
158
 
 
159
 
    @deprecated_function(zero_eight)
 
155
        """Create a new branch, rooted at 'base' (url)"""
 
156
        t = get_transport(base)
 
157
        return _Branch(t, init=True)
 
158
 
160
159
    def setup_caching(self, cache_root):
161
160
        """Subclasses that care about caching should override this, and set
162
161
        up cached stores located under cache_root.
 
162
        """
 
163
        self.cache_root = cache_root
 
164
 
 
165
 
 
166
class _Branch(Branch):
 
167
    """A branch stored in the actual filesystem.
 
168
 
 
169
    Note that it's "local" in the context of the filesystem; it doesn't
 
170
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
171
    it's writable, and can be accessed via the normal filesystem API.
 
172
 
 
173
    _lock_mode
 
174
        None, or 'r' or 'w'
 
175
 
 
176
    _lock_count
 
177
        If _lock_mode is true, a positive count of the number of times the
 
178
        lock has been taken.
 
179
 
 
180
    _lock
 
181
        Lock object from bzrlib.lock.
 
182
    """
 
183
    # We actually expect this class to be somewhat short-lived; part of its
 
184
    # purpose is to try to isolate what bits of the branch logic are tied to
 
185
    # filesystem access, so that in a later step, we can extricate them to
 
186
    # a separarte ("storage") class.
 
187
    _lock_mode = None
 
188
    _lock_count = None
 
189
    _lock = None
 
190
    _inventory_weave = None
 
191
    
 
192
    # Map some sort of prefix into a namespace
 
193
    # stuff like "revno:10", "revid:", etc.
 
194
    # This should match a prefix with a function which accepts
 
195
    REVISION_NAMESPACES = {}
 
196
 
 
197
    def push_stores(self, branch_to):
 
198
        """Copy the content of this branches store to branch_to."""
 
199
        if (self._branch_format != branch_to._branch_format
 
200
            or self._branch_format != 4):
 
201
            from bzrlib.fetch import greedy_fetch
 
202
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
203
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
204
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
205
                         revision=self.last_revision())
 
206
            return
 
207
 
 
208
        store_pairs = ((self.text_store,      branch_to.text_store),
 
209
                       (self.inventory_store, branch_to.inventory_store),
 
210
                       (self.revision_store,  branch_to.revision_store))
 
211
        try:
 
212
            for from_store, to_store in store_pairs: 
 
213
                copy_all(from_store, to_store)
 
214
        except UnlistableStore:
 
215
            raise UnlistableBranch(from_store)
 
216
 
 
217
    def __init__(self, transport, init=False,
 
218
                 relax_version_check=False):
 
219
        """Create new branch object at a particular location.
 
220
 
 
221
        transport -- A Transport object, defining how to access files.
 
222
                (If a string, transport.transport() will be used to
 
223
                create a Transport object)
163
224
        
164
 
        NOTE: This is unused.
165
 
        """
166
 
        pass
167
 
 
168
 
    def get_config(self):
169
 
        return BranchConfig(self)
170
 
 
171
 
    def _get_nick(self):
172
 
        return self.get_config().get_nickname()
173
 
 
174
 
    def _set_nick(self, nick):
175
 
        self.get_config().set_user_option('nickname', nick)
176
 
 
177
 
    nick = property(_get_nick, _set_nick)
178
 
 
179
 
    def is_locked(self):
180
 
        raise NotImplementedError(self.is_locked)
 
225
        init -- If True, create new control files in a previously
 
226
             unversioned directory.  If False, the branch must already
 
227
             be versioned.
 
228
 
 
229
        relax_version_check -- If true, the usual check for the branch
 
230
            version is not applied.  This is intended only for
 
231
            upgrade/recovery type use; it's not guaranteed that
 
232
            all operations will work on old format branches.
 
233
 
 
234
        In the test suite, creation of new trees is tested using the
 
235
        `ScratchBranch` class.
 
236
        """
 
237
        assert isinstance(transport, Transport), \
 
238
            "%r is not a Transport" % transport
 
239
        self._transport = transport
 
240
        if init:
 
241
            self._make_control()
 
242
        self._check_format(relax_version_check)
 
243
 
 
244
        def get_store(name, compressed=True, prefixed=False):
 
245
            # FIXME: This approach of assuming stores are all entirely compressed
 
246
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
247
            # some existing branches where there's a mixture; we probably 
 
248
            # still want the option to look for both.
 
249
            relpath = self._rel_controlfilename(name)
 
250
            if compressed:
 
251
                store = CompressedTextStore(self._transport.clone(relpath),
 
252
                                            prefixed=prefixed)
 
253
            else:
 
254
                store = TextStore(self._transport.clone(relpath),
 
255
                                  prefixed=prefixed)
 
256
            #if self._transport.should_cache():
 
257
            #    cache_path = os.path.join(self.cache_root, name)
 
258
            #    os.mkdir(cache_path)
 
259
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
260
            return store
 
261
        def get_weave(name, prefixed=False):
 
262
            relpath = self._rel_controlfilename(name)
 
263
            ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
 
264
            if self._transport.should_cache():
 
265
                ws.enable_cache = True
 
266
            return ws
 
267
 
 
268
        if self._branch_format == 4:
 
269
            self.inventory_store = get_store('inventory-store')
 
270
            self.text_store = get_store('text-store')
 
271
            self.revision_store = get_store('revision-store')
 
272
        elif self._branch_format == 5:
 
273
            self.control_weaves = get_weave([])
 
274
            self.weave_store = get_weave('weaves')
 
275
            self.revision_store = get_store('revision-store', compressed=False)
 
276
        elif self._branch_format == 6:
 
277
            self.control_weaves = get_weave([])
 
278
            self.weave_store = get_weave('weaves', prefixed=True)
 
279
            self.revision_store = get_store('revision-store', compressed=False,
 
280
                                            prefixed=True)
 
281
        self._transaction = None
 
282
 
 
283
    def __str__(self):
 
284
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
 
285
 
 
286
 
 
287
    __repr__ = __str__
 
288
 
 
289
 
 
290
    def __del__(self):
 
291
        if self._lock_mode or self._lock:
 
292
            # XXX: This should show something every time, and be suitable for
 
293
            # headless operation and embedding
 
294
            warn("branch %r was not explicitly unlocked" % self)
 
295
            self._lock.unlock()
 
296
 
 
297
        # TODO: It might be best to do this somewhere else,
 
298
        # but it is nice for a Branch object to automatically
 
299
        # cache it's information.
 
300
        # Alternatively, we could have the Transport objects cache requests
 
301
        # See the earlier discussion about how major objects (like Branch)
 
302
        # should never expect their __del__ function to run.
 
303
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
304
            try:
 
305
                import shutil
 
306
                shutil.rmtree(self.cache_root)
 
307
            except:
 
308
                pass
 
309
            self.cache_root = None
 
310
 
 
311
    def _get_base(self):
 
312
        if self._transport:
 
313
            return self._transport.base
 
314
        return None
 
315
 
 
316
    base = property(_get_base)
 
317
 
 
318
    def _finish_transaction(self):
 
319
        """Exit the current transaction."""
 
320
        if self._transaction is None:
 
321
            raise errors.LockError('Branch %s is not in a transaction' %
 
322
                                   self)
 
323
        transaction = self._transaction
 
324
        self._transaction = None
 
325
        transaction.finish()
 
326
 
 
327
    def get_transaction(self):
 
328
        """Return the current active transaction.
 
329
 
 
330
        If no transaction is active, this returns a passthrough object
 
331
        for which all data is immedaitely flushed and no caching happens.
 
332
        """
 
333
        if self._transaction is None:
 
334
            return transactions.PassThroughTransaction()
 
335
        else:
 
336
            return self._transaction
 
337
 
 
338
    def _set_transaction(self, new_transaction):
 
339
        """Set a new active transaction."""
 
340
        if self._transaction is not None:
 
341
            raise errors.LockError('Branch %s is in a transaction already.' %
 
342
                                   self)
 
343
        self._transaction = new_transaction
181
344
 
182
345
    def lock_write(self):
183
 
        raise NotImplementedError(self.lock_write)
 
346
        # TODO: Upgrade locking to support using a Transport,
 
347
        # and potentially a remote locking protocol
 
348
        if self._lock_mode:
 
349
            if self._lock_mode != 'w':
 
350
                raise LockError("can't upgrade to a write lock from %r" %
 
351
                                self._lock_mode)
 
352
            self._lock_count += 1
 
353
        else:
 
354
            self._lock = self._transport.lock_write(
 
355
                    self._rel_controlfilename('branch-lock'))
 
356
            self._lock_mode = 'w'
 
357
            self._lock_count = 1
 
358
            self._set_transaction(transactions.PassThroughTransaction())
 
359
 
184
360
 
185
361
    def lock_read(self):
186
 
        raise NotImplementedError(self.lock_read)
187
 
 
 
362
        if self._lock_mode:
 
363
            assert self._lock_mode in ('r', 'w'), \
 
364
                   "invalid lock mode %r" % self._lock_mode
 
365
            self._lock_count += 1
 
366
        else:
 
367
            self._lock = self._transport.lock_read(
 
368
                    self._rel_controlfilename('branch-lock'))
 
369
            self._lock_mode = 'r'
 
370
            self._lock_count = 1
 
371
            self._set_transaction(transactions.ReadOnlyTransaction())
 
372
                        
188
373
    def unlock(self):
189
 
        raise NotImplementedError(self.unlock)
190
 
 
191
 
    def peek_lock_mode(self):
192
 
        """Return lock mode for the Branch: 'r', 'w' or None"""
193
 
        raise NotImplementedError(self.peek_lock_mode)
194
 
 
195
 
    def get_physical_lock_status(self):
196
 
        raise NotImplementedError(self.get_physical_lock_status)
197
 
 
198
 
    def leave_lock_in_place(self):
199
 
        """Tell this branch object not to release the physical lock when this
200
 
        object is unlocked.
201
 
        
202
 
        If lock_write doesn't return a token, then this method is not supported.
203
 
        """
204
 
        self.control_files.leave_in_place()
205
 
 
206
 
    def dont_leave_lock_in_place(self):
207
 
        """Tell this branch object to release the physical lock when this
208
 
        object is unlocked, even if it didn't originally acquire it.
209
 
 
210
 
        If lock_write doesn't return a token, then this method is not supported.
211
 
        """
212
 
        self.control_files.dont_leave_in_place()
 
374
        if not self._lock_mode:
 
375
            raise LockError('branch %r is not locked' % (self))
 
376
 
 
377
        if self._lock_count > 1:
 
378
            self._lock_count -= 1
 
379
        else:
 
380
            self._finish_transaction()
 
381
            self._lock.unlock()
 
382
            self._lock = None
 
383
            self._lock_mode = self._lock_count = None
213
384
 
214
385
    def abspath(self, name):
215
 
        """Return absolute filename for something in the branch
216
 
        
217
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
218
 
        method and not a tree method.
219
 
        """
220
 
        raise NotImplementedError(self.abspath)
221
 
 
222
 
    def bind(self, other):
223
 
        """Bind the local branch the other branch.
224
 
 
225
 
        :param other: The branch to bind to
226
 
        :type other: Branch
227
 
        """
228
 
        raise errors.UpgradeRequired(self.base)
229
 
 
230
 
    @needs_write_lock
231
 
    def fetch(self, from_branch, last_revision=None, pb=None):
232
 
        """Copy revisions from from_branch into this branch.
233
 
 
234
 
        :param from_branch: Where to copy from.
235
 
        :param last_revision: What revision to stop at (None for at the end
236
 
                              of the branch.
237
 
        :param pb: An optional progress bar to use.
238
 
 
239
 
        Returns the copied revision count and the failed revisions in a tuple:
240
 
        (copied, failures).
241
 
        """
242
 
        if self.base == from_branch.base:
243
 
            return (0, [])
244
 
        if pb is None:
245
 
            nested_pb = ui.ui_factory.nested_progress_bar()
246
 
            pb = nested_pb
247
 
        else:
248
 
            nested_pb = None
249
 
 
250
 
        from_branch.lock_read()
251
 
        try:
252
 
            if last_revision is None:
253
 
                pb.update('get source history')
254
 
                last_revision = from_branch.last_revision()
255
 
                if last_revision is None:
256
 
                    last_revision = _mod_revision.NULL_REVISION
257
 
            return self.repository.fetch(from_branch.repository,
258
 
                                         revision_id=last_revision,
259
 
                                         pb=nested_pb)
260
 
        finally:
261
 
            if nested_pb is not None:
262
 
                nested_pb.finished()
263
 
            from_branch.unlock()
264
 
 
265
 
    def get_bound_location(self):
266
 
        """Return the URL of the branch we are bound to.
267
 
 
268
 
        Older format branches cannot bind, please be sure to use a metadir
269
 
        branch.
270
 
        """
271
 
        return None
272
 
    
273
 
    def get_old_bound_location(self):
274
 
        """Return the URL of the branch we used to be bound to
275
 
        """
276
 
        raise errors.UpgradeRequired(self.base)
277
 
 
278
 
    def get_commit_builder(self, parents, config=None, timestamp=None, 
279
 
                           timezone=None, committer=None, revprops=None, 
280
 
                           revision_id=None):
281
 
        """Obtain a CommitBuilder for this branch.
282
 
        
283
 
        :param parents: Revision ids of the parents of the new revision.
284
 
        :param config: Optional configuration to use.
285
 
        :param timestamp: Optional timestamp recorded for commit.
286
 
        :param timezone: Optional timezone for timestamp.
287
 
        :param committer: Optional committer to set for commit.
288
 
        :param revprops: Optional dictionary of revision properties.
289
 
        :param revision_id: Optional revision id.
290
 
        """
291
 
 
292
 
        if config is None:
293
 
            config = self.get_config()
294
 
        
295
 
        return self.repository.get_commit_builder(self, parents, config,
296
 
            timestamp, timezone, committer, revprops, revision_id)
297
 
 
298
 
    def get_master_branch(self):
299
 
        """Return the branch we are bound to.
300
 
        
301
 
        :return: Either a Branch, or None
302
 
        """
303
 
        return None
 
386
        """Return absolute filename for something in the branch"""
 
387
        return self._transport.abspath(name)
 
388
 
 
389
    def relpath(self, path):
 
390
        """Return path relative to this branch of something inside it.
 
391
 
 
392
        Raises an error if path is not in this branch."""
 
393
        return self._transport.relpath(path)
 
394
 
 
395
 
 
396
    def _rel_controlfilename(self, file_or_path):
 
397
        if isinstance(file_or_path, basestring):
 
398
            file_or_path = [file_or_path]
 
399
        return [bzrlib.BZRDIR] + file_or_path
 
400
 
 
401
    def controlfilename(self, file_or_path):
 
402
        """Return location relative to branch."""
 
403
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
 
404
 
 
405
 
 
406
    def controlfile(self, file_or_path, mode='r'):
 
407
        """Open a control file for this branch.
 
408
 
 
409
        There are two classes of file in the control directory: text
 
410
        and binary.  binary files are untranslated byte streams.  Text
 
411
        control files are stored with Unix newlines and in UTF-8, even
 
412
        if the platform or locale defaults are different.
 
413
 
 
414
        Controlfiles should almost never be opened in write mode but
 
415
        rather should be atomically copied and replaced using atomicfile.
 
416
        """
 
417
        import codecs
 
418
 
 
419
        relpath = self._rel_controlfilename(file_or_path)
 
420
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
421
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
422
        if mode == 'rb': 
 
423
            return self._transport.get(relpath)
 
424
        elif mode == 'wb':
 
425
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
 
426
        elif mode == 'r':
 
427
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
428
        elif mode == 'w':
 
429
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
 
430
        else:
 
431
            raise BzrError("invalid controlfile mode %r" % mode)
 
432
 
 
433
    def put_controlfile(self, path, f, encode=True):
 
434
        """Write an entry as a controlfile.
 
435
 
 
436
        :param path: The path to put the file, relative to the .bzr control
 
437
                     directory
 
438
        :param f: A file-like or string object whose contents should be copied.
 
439
        :param encode:  If true, encode the contents as utf-8
 
440
        """
 
441
        self.put_controlfiles([(path, f)], encode=encode)
 
442
 
 
443
    def put_controlfiles(self, files, encode=True):
 
444
        """Write several entries as controlfiles.
 
445
 
 
446
        :param files: A list of [(path, file)] pairs, where the path is the directory
 
447
                      underneath the bzr control directory
 
448
        :param encode:  If true, encode the contents as utf-8
 
449
        """
 
450
        import codecs
 
451
        ctrl_files = []
 
452
        for path, f in files:
 
453
            if encode:
 
454
                if isinstance(f, basestring):
 
455
                    f = f.encode('utf-8', 'replace')
 
456
                else:
 
457
                    f = codecs.getwriter('utf-8')(f, errors='replace')
 
458
            path = self._rel_controlfilename(path)
 
459
            ctrl_files.append((path, f))
 
460
        self._transport.put_multi(ctrl_files)
 
461
 
 
462
    def _make_control(self):
 
463
        from bzrlib.inventory import Inventory
 
464
        from bzrlib.weavefile import write_weave_v5
 
465
        from bzrlib.weave import Weave
 
466
        
 
467
        # Create an empty inventory
 
468
        sio = StringIO()
 
469
        # if we want per-tree root ids then this is the place to set
 
470
        # them; they're not needed for now and so ommitted for
 
471
        # simplicity.
 
472
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
473
        empty_inv = sio.getvalue()
 
474
        sio = StringIO()
 
475
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
476
        empty_weave = sio.getvalue()
 
477
 
 
478
        dirs = [[], 'revision-store', 'weaves']
 
479
        files = [('README', 
 
480
            "This is a Bazaar-NG control directory.\n"
 
481
            "Do not change any files in this directory.\n"),
 
482
            ('branch-format', BZR_BRANCH_FORMAT_6),
 
483
            ('revision-history', ''),
 
484
            ('branch-name', ''),
 
485
            ('branch-lock', ''),
 
486
            ('pending-merges', ''),
 
487
            ('inventory', empty_inv),
 
488
            ('inventory.weave', empty_weave),
 
489
            ('ancestry.weave', empty_weave)
 
490
        ]
 
491
        cfn = self._rel_controlfilename
 
492
        self._transport.mkdir_multi([cfn(d) for d in dirs])
 
493
        self.put_controlfiles(files)
 
494
        mutter('created control directory in ' + self._transport.base)
 
495
 
 
496
    def _check_format(self, relax_version_check):
 
497
        """Check this branch format is supported.
 
498
 
 
499
        The format level is stored, as an integer, in
 
500
        self._branch_format for code that needs to check it later.
 
501
 
 
502
        In the future, we might need different in-memory Branch
 
503
        classes to support downlevel branches.  But not yet.
 
504
        """
 
505
        try:
 
506
            fmt = self.controlfile('branch-format', 'r').read()
 
507
        except NoSuchFile:
 
508
            raise NotBranchError(self.base)
 
509
        mutter("got branch format %r", fmt)
 
510
        if fmt == BZR_BRANCH_FORMAT_6:
 
511
            self._branch_format = 6
 
512
        elif fmt == BZR_BRANCH_FORMAT_5:
 
513
            self._branch_format = 5
 
514
        elif fmt == BZR_BRANCH_FORMAT_4:
 
515
            self._branch_format = 4
 
516
 
 
517
        if (not relax_version_check
 
518
            and self._branch_format not in (5, 6)):
 
519
            raise errors.UnsupportedFormatError(
 
520
                           'sorry, branch format %r not supported' % fmt,
 
521
                           ['use a different bzr version',
 
522
                            'or remove the .bzr directory'
 
523
                            ' and "bzr init" again'])
 
524
 
 
525
    def get_root_id(self):
 
526
        """Return the id of this branches root"""
 
527
        inv = self.read_working_inventory()
 
528
        return inv.root.file_id
 
529
 
 
530
    def set_root_id(self, file_id):
 
531
        inv = self.read_working_inventory()
 
532
        orig_root_id = inv.root.file_id
 
533
        del inv._byid[inv.root.file_id]
 
534
        inv.root.file_id = file_id
 
535
        inv._byid[inv.root.file_id] = inv.root
 
536
        for fid in inv:
 
537
            entry = inv[fid]
 
538
            if entry.parent_id in (None, orig_root_id):
 
539
                entry.parent_id = inv.root.file_id
 
540
        self._write_inventory(inv)
 
541
 
 
542
    def read_working_inventory(self):
 
543
        """Read the working inventory."""
 
544
        self.lock_read()
 
545
        try:
 
546
            # ElementTree does its own conversion from UTF-8, so open in
 
547
            # binary.
 
548
            f = self.controlfile('inventory', 'rb')
 
549
            return bzrlib.xml5.serializer_v5.read_inventory(f)
 
550
        finally:
 
551
            self.unlock()
 
552
            
 
553
 
 
554
    def _write_inventory(self, inv):
 
555
        """Update the working inventory.
 
556
 
 
557
        That is to say, the inventory describing changes underway, that
 
558
        will be committed to the next revision.
 
559
        """
 
560
        from cStringIO import StringIO
 
561
        self.lock_write()
 
562
        try:
 
563
            sio = StringIO()
 
564
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
565
            sio.seek(0)
 
566
            # Transport handles atomicity
 
567
            self.put_controlfile('inventory', sio)
 
568
        finally:
 
569
            self.unlock()
 
570
        
 
571
        mutter('wrote working inventory')
 
572
            
 
573
    inventory = property(read_working_inventory, _write_inventory, None,
 
574
                         """Inventory for the working copy.""")
 
575
 
 
576
    def add(self, files, ids=None):
 
577
        """Make files versioned.
 
578
 
 
579
        Note that the command line normally calls smart_add instead,
 
580
        which can automatically recurse.
 
581
 
 
582
        This puts the files in the Added state, so that they will be
 
583
        recorded by the next commit.
 
584
 
 
585
        files
 
586
            List of paths to add, relative to the base of the tree.
 
587
 
 
588
        ids
 
589
            If set, use these instead of automatically generated ids.
 
590
            Must be the same length as the list of files, but may
 
591
            contain None for ids that are to be autogenerated.
 
592
 
 
593
        TODO: Perhaps have an option to add the ids even if the files do
 
594
              not (yet) exist.
 
595
 
 
596
        TODO: Perhaps yield the ids and paths as they're added.
 
597
        """
 
598
        # TODO: Re-adding a file that is removed in the working copy
 
599
        # should probably put it back with the previous ID.
 
600
        if isinstance(files, basestring):
 
601
            assert(ids is None or isinstance(ids, basestring))
 
602
            files = [files]
 
603
            if ids is not None:
 
604
                ids = [ids]
 
605
 
 
606
        if ids is None:
 
607
            ids = [None] * len(files)
 
608
        else:
 
609
            assert(len(ids) == len(files))
 
610
 
 
611
        self.lock_write()
 
612
        try:
 
613
            inv = self.read_working_inventory()
 
614
            for f,file_id in zip(files, ids):
 
615
                if is_control_file(f):
 
616
                    raise BzrError("cannot add control file %s" % quotefn(f))
 
617
 
 
618
                fp = splitpath(f)
 
619
 
 
620
                if len(fp) == 0:
 
621
                    raise BzrError("cannot add top-level %r" % f)
 
622
 
 
623
                fullpath = os.path.normpath(self.abspath(f))
 
624
 
 
625
                try:
 
626
                    kind = file_kind(fullpath)
 
627
                except OSError:
 
628
                    # maybe something better?
 
629
                    raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
630
 
 
631
                if not InventoryEntry.versionable_kind(kind):
 
632
                    raise BzrError('cannot add: not a versionable file ('
 
633
                                   'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
634
 
 
635
                if file_id is None:
 
636
                    file_id = gen_file_id(f)
 
637
                inv.add_path(f, kind=kind, file_id=file_id)
 
638
 
 
639
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
640
 
 
641
            self._write_inventory(inv)
 
642
        finally:
 
643
            self.unlock()
 
644
            
 
645
 
 
646
    def print_file(self, file, revno):
 
647
        """Print `file` to stdout."""
 
648
        self.lock_read()
 
649
        try:
 
650
            tree = self.revision_tree(self.get_rev_id(revno))
 
651
            # use inventory as it was in that revision
 
652
            file_id = tree.inventory.path2id(file)
 
653
            if not file_id:
 
654
                raise BzrError("%r is not present in revision %s" % (file, revno))
 
655
            tree.print_file(file_id)
 
656
        finally:
 
657
            self.unlock()
 
658
 
 
659
 
 
660
    def remove(self, files, verbose=False):
 
661
        """Mark nominated files for removal from the inventory.
 
662
 
 
663
        This does not remove their text.  This does not run on 
 
664
 
 
665
        TODO: Refuse to remove modified files unless --force is given?
 
666
 
 
667
        TODO: Do something useful with directories.
 
668
 
 
669
        TODO: Should this remove the text or not?  Tough call; not
 
670
        removing may be useful and the user can just use use rm, and
 
671
        is the opposite of add.  Removing it is consistent with most
 
672
        other tools.  Maybe an option.
 
673
        """
 
674
        ## TODO: Normalize names
 
675
        ## TODO: Remove nested loops; better scalability
 
676
        if isinstance(files, basestring):
 
677
            files = [files]
 
678
 
 
679
        self.lock_write()
 
680
 
 
681
        try:
 
682
            tree = self.working_tree()
 
683
            inv = tree.inventory
 
684
 
 
685
            # do this before any modifications
 
686
            for f in files:
 
687
                fid = inv.path2id(f)
 
688
                if not fid:
 
689
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
 
690
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
 
691
                if verbose:
 
692
                    # having remove it, it must be either ignored or unknown
 
693
                    if tree.is_ignored(f):
 
694
                        new_status = 'I'
 
695
                    else:
 
696
                        new_status = '?'
 
697
                    show_status(new_status, inv[fid].kind, quotefn(f))
 
698
                del inv[fid]
 
699
 
 
700
            self._write_inventory(inv)
 
701
        finally:
 
702
            self.unlock()
 
703
 
 
704
    # FIXME: this doesn't need to be a branch method
 
705
    def set_inventory(self, new_inventory_list):
 
706
        from bzrlib.inventory import Inventory, InventoryEntry
 
707
        inv = Inventory(self.get_root_id())
 
708
        for path, file_id, parent, kind in new_inventory_list:
 
709
            name = os.path.basename(path)
 
710
            if name == "":
 
711
                continue
 
712
            # fixme, there should be a factory function inv,add_?? 
 
713
            if kind == 'directory':
 
714
                inv.add(inventory.InventoryDirectory(file_id, name, parent))
 
715
            elif kind == 'file':
 
716
                inv.add(inventory.InventoryFile(file_id, name, parent))
 
717
            elif kind == 'symlink':
 
718
                inv.add(inventory.InventoryLink(file_id, name, parent))
 
719
            else:
 
720
                raise BzrError("unknown kind %r" % kind)
 
721
        self._write_inventory(inv)
 
722
 
 
723
    def unknowns(self):
 
724
        """Return all unknown files.
 
725
 
 
726
        These are files in the working directory that are not versioned or
 
727
        control files or ignored.
 
728
        
 
729
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
730
        >>> list(b.unknowns())
 
731
        ['foo']
 
732
        >>> b.add('foo')
 
733
        >>> list(b.unknowns())
 
734
        []
 
735
        >>> b.remove('foo')
 
736
        >>> list(b.unknowns())
 
737
        ['foo']
 
738
        """
 
739
        return self.working_tree().unknowns()
 
740
 
 
741
 
 
742
    def append_revision(self, *revision_ids):
 
743
        for revision_id in revision_ids:
 
744
            mutter("add {%s} to revision-history" % revision_id)
 
745
        self.lock_write()
 
746
        try:
 
747
            rev_history = self.revision_history()
 
748
            rev_history.extend(revision_ids)
 
749
            self.put_controlfile('revision-history', '\n'.join(rev_history))
 
750
        finally:
 
751
            self.unlock()
 
752
 
 
753
    def has_revision(self, revision_id):
 
754
        """True if this branch has a copy of the revision.
 
755
 
 
756
        This does not necessarily imply the revision is merge
 
757
        or on the mainline."""
 
758
        return (revision_id is None
 
759
                or revision_id in self.revision_store)
 
760
 
 
761
    def get_revision_xml_file(self, revision_id):
 
762
        """Return XML file object for revision object."""
 
763
        if not revision_id or not isinstance(revision_id, basestring):
 
764
            raise InvalidRevisionId(revision_id)
 
765
 
 
766
        self.lock_read()
 
767
        try:
 
768
            try:
 
769
                return self.revision_store[revision_id]
 
770
            except (IndexError, KeyError):
 
771
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
772
        finally:
 
773
            self.unlock()
 
774
 
 
775
    #deprecated
 
776
    get_revision_xml = get_revision_xml_file
 
777
 
 
778
    def get_revision_xml(self, revision_id):
 
779
        return self.get_revision_xml_file(revision_id).read()
 
780
 
 
781
 
 
782
    def get_revision(self, revision_id):
 
783
        """Return the Revision object for a named revision"""
 
784
        xml_file = self.get_revision_xml_file(revision_id)
 
785
 
 
786
        try:
 
787
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
788
        except SyntaxError, e:
 
789
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
790
                                         [revision_id,
 
791
                                          str(e)])
 
792
            
 
793
        assert r.revision_id == revision_id
 
794
        return r
304
795
 
305
796
    def get_revision_delta(self, revno):
306
797
        """Return the delta for one revision.
312
803
        rh = self.revision_history()
313
804
        if not (1 <= revno <= len(rh)):
314
805
            raise InvalidRevisionNumber(revno)
315
 
        return self.repository.get_revision_delta(rh[revno-1])
316
 
 
317
 
    @deprecated_method(zero_sixteen)
318
 
    def get_root_id(self):
319
 
        """Return the id of this branches root
320
 
 
321
 
        Deprecated: branches don't have root ids-- trees do.
322
 
        Use basis_tree().get_root_id() instead.
323
 
        """
324
 
        raise NotImplementedError(self.get_root_id)
325
 
 
326
 
    def print_file(self, file, revision_id):
327
 
        """Print `file` to stdout."""
328
 
        raise NotImplementedError(self.print_file)
329
 
 
330
 
    def append_revision(self, *revision_ids):
331
 
        raise NotImplementedError(self.append_revision)
332
 
 
333
 
    def set_revision_history(self, rev_history):
334
 
        raise NotImplementedError(self.set_revision_history)
335
 
 
336
 
    def _cache_revision_history(self, rev_history):
337
 
        """Set the cached revision history to rev_history.
338
 
 
339
 
        The revision_history method will use this cache to avoid regenerating
340
 
        the revision history.
341
 
 
342
 
        This API is semi-public; it only for use by subclasses, all other code
343
 
        should consider it to be private.
344
 
        """
345
 
        self._revision_history_cache = rev_history
346
 
 
347
 
    def _clear_cached_state(self):
348
 
        """Clear any cached data on this branch, e.g. cached revision history.
349
 
 
350
 
        This means the next call to revision_history will need to call
351
 
        _gen_revision_history.
352
 
 
353
 
        This API is semi-public; it only for use by subclasses, all other code
354
 
        should consider it to be private.
355
 
        """
356
 
        self._revision_history_cache = None
357
 
 
358
 
    def _gen_revision_history(self):
359
 
        """Return sequence of revision hashes on to this branch.
360
 
        
361
 
        Unlike revision_history, this method always regenerates or rereads the
362
 
        revision history, i.e. it does not cache the result, so repeated calls
363
 
        may be expensive.
364
 
 
365
 
        Concrete subclasses should override this instead of revision_history so
366
 
        that subclasses do not need to deal with caching logic.
367
 
        
368
 
        This API is semi-public; it only for use by subclasses, all other code
369
 
        should consider it to be private.
370
 
        """
371
 
        raise NotImplementedError(self._gen_revision_history)
372
 
 
373
 
    @needs_read_lock
 
806
 
 
807
        # revno is 1-based; list is 0-based
 
808
 
 
809
        new_tree = self.revision_tree(rh[revno-1])
 
810
        if revno == 1:
 
811
            old_tree = EmptyTree()
 
812
        else:
 
813
            old_tree = self.revision_tree(rh[revno-2])
 
814
 
 
815
        return compare_trees(old_tree, new_tree)
 
816
 
 
817
    def get_revision_sha1(self, revision_id):
 
818
        """Hash the stored value of a revision, and return it."""
 
819
        # In the future, revision entries will be signed. At that
 
820
        # point, it is probably best *not* to include the signature
 
821
        # in the revision hash. Because that lets you re-sign
 
822
        # the revision, (add signatures/remove signatures) and still
 
823
        # have all hash pointers stay consistent.
 
824
        # But for now, just hash the contents.
 
825
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
826
 
 
827
    def get_ancestry(self, revision_id):
 
828
        """Return a list of revision-ids integrated by a revision.
 
829
        
 
830
        This currently returns a list, but the ordering is not guaranteed:
 
831
        treat it as a set.
 
832
        """
 
833
        if revision_id is None:
 
834
            return [None]
 
835
        w = self.get_inventory_weave()
 
836
        return [None] + map(w.idx_to_name,
 
837
                            w.inclusions([w.lookup(revision_id)]))
 
838
 
 
839
    def get_inventory_weave(self):
 
840
        return self.control_weaves.get_weave('inventory',
 
841
                                             self.get_transaction())
 
842
 
 
843
    def get_inventory(self, revision_id):
 
844
        """Get Inventory object by hash."""
 
845
        xml = self.get_inventory_xml(revision_id)
 
846
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
847
 
 
848
    def get_inventory_xml(self, revision_id):
 
849
        """Get inventory XML as a file object."""
 
850
        try:
 
851
            assert isinstance(revision_id, basestring), type(revision_id)
 
852
            iw = self.get_inventory_weave()
 
853
            return iw.get_text(iw.lookup(revision_id))
 
854
        except IndexError:
 
855
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
856
 
 
857
    def get_inventory_sha1(self, revision_id):
 
858
        """Return the sha1 hash of the inventory entry
 
859
        """
 
860
        return self.get_revision(revision_id).inventory_sha1
 
861
 
 
862
    def get_revision_inventory(self, revision_id):
 
863
        """Return inventory of a past revision."""
 
864
        # TODO: Unify this with get_inventory()
 
865
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
866
        # must be the same as its revision, so this is trivial.
 
867
        if revision_id == None:
 
868
            return Inventory(self.get_root_id())
 
869
        else:
 
870
            return self.get_inventory(revision_id)
 
871
 
374
872
    def revision_history(self):
375
 
        """Return sequence of revision hashes on to this branch.
376
 
        
377
 
        This method will cache the revision history for as long as it is safe to
378
 
        do so.
379
 
        """
380
 
        if self._revision_history_cache is not None:
381
 
            history = self._revision_history_cache
382
 
        else:
383
 
            history = self._gen_revision_history()
384
 
            self._cache_revision_history(history)
385
 
        return list(history)
 
873
        """Return sequence of revision hashes on to this branch."""
 
874
        self.lock_read()
 
875
        try:
 
876
            return [l.rstrip('\r\n') for l in
 
877
                    self.controlfile('revision-history', 'r').readlines()]
 
878
        finally:
 
879
            self.unlock()
 
880
 
 
881
    def common_ancestor(self, other, self_revno=None, other_revno=None):
 
882
        """
 
883
        >>> from bzrlib.commit import commit
 
884
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
 
885
        >>> sb.common_ancestor(sb) == (None, None)
 
886
        True
 
887
        >>> commit(sb, "Committing first revision", verbose=False)
 
888
        >>> sb.common_ancestor(sb)[0]
 
889
        1
 
890
        >>> clone = sb.clone()
 
891
        >>> commit(sb, "Committing second revision", verbose=False)
 
892
        >>> sb.common_ancestor(sb)[0]
 
893
        2
 
894
        >>> sb.common_ancestor(clone)[0]
 
895
        1
 
896
        >>> commit(clone, "Committing divergent second revision", 
 
897
        ...               verbose=False)
 
898
        >>> sb.common_ancestor(clone)[0]
 
899
        1
 
900
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
 
901
        True
 
902
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
 
903
        True
 
904
        >>> clone2 = sb.clone()
 
905
        >>> sb.common_ancestor(clone2)[0]
 
906
        2
 
907
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
 
908
        1
 
909
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
 
910
        1
 
911
        """
 
912
        my_history = self.revision_history()
 
913
        other_history = other.revision_history()
 
914
        if self_revno is None:
 
915
            self_revno = len(my_history)
 
916
        if other_revno is None:
 
917
            other_revno = len(other_history)
 
918
        indices = range(min((self_revno, other_revno)))
 
919
        indices.reverse()
 
920
        for r in indices:
 
921
            if my_history[r] == other_history[r]:
 
922
                return r+1, my_history[r]
 
923
        return None, None
 
924
 
386
925
 
387
926
    def revno(self):
388
927
        """Return current revision number for this branch.
392
931
        """
393
932
        return len(self.revision_history())
394
933
 
395
 
    def unbind(self):
396
 
        """Older format branches cannot bind or unbind."""
397
 
        raise errors.UpgradeRequired(self.base)
398
 
 
399
 
    def set_append_revisions_only(self, enabled):
400
 
        """Older format branches are never restricted to append-only"""
401
 
        raise errors.UpgradeRequired(self.base)
402
934
 
403
935
    def last_revision(self):
404
 
        """Return last revision id, or None"""
 
936
        """Return last patch hash, or None if no history.
 
937
        """
405
938
        ph = self.revision_history()
406
939
        if ph:
407
940
            return ph[-1]
408
941
        else:
409
942
            return None
410
943
 
411
 
    def last_revision_info(self):
412
 
        """Return information about the last revision.
413
 
 
414
 
        :return: A tuple (revno, last_revision_id).
415
 
        """
416
 
        rh = self.revision_history()
417
 
        revno = len(rh)
418
 
        if revno:
419
 
            return (revno, rh[-1])
420
 
        else:
421
 
            return (0, _mod_revision.NULL_REVISION)
422
 
 
423
 
    def missing_revisions(self, other, stop_revision=None):
 
944
 
 
945
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
424
946
        """Return a list of new revisions that would perfectly fit.
425
947
        
426
948
        If self and other have not diverged, return a list of the revisions
427
949
        present in other, but missing from self.
 
950
 
 
951
        >>> from bzrlib.commit import commit
 
952
        >>> bzrlib.trace.silent = True
 
953
        >>> br1 = ScratchBranch()
 
954
        >>> br2 = ScratchBranch()
 
955
        >>> br1.missing_revisions(br2)
 
956
        []
 
957
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
958
        >>> br1.missing_revisions(br2)
 
959
        [u'REVISION-ID-1']
 
960
        >>> br2.missing_revisions(br1)
 
961
        []
 
962
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
963
        >>> br1.missing_revisions(br2)
 
964
        []
 
965
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
966
        >>> br1.missing_revisions(br2)
 
967
        [u'REVISION-ID-2A']
 
968
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
969
        >>> br1.missing_revisions(br2)
 
970
        Traceback (most recent call last):
 
971
        DivergedBranches: These branches have diverged.
428
972
        """
 
973
        # FIXME: If the branches have diverged, but the latest
 
974
        # revision in this branch is completely merged into the other,
 
975
        # then we should still be able to pull.
429
976
        self_history = self.revision_history()
430
977
        self_len = len(self_history)
431
978
        other_history = other.revision_history()
440
987
        else:
441
988
            assert isinstance(stop_revision, int)
442
989
            if stop_revision > other_len:
443
 
                raise errors.NoSuchRevision(self, stop_revision)
 
990
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
444
991
        return other_history[self_len:stop_revision]
445
992
 
446
993
    def update_revisions(self, other, stop_revision=None):
447
 
        """Pull in new perfect-fit revisions.
448
 
 
449
 
        :param other: Another Branch to pull from
450
 
        :param stop_revision: Updated until the given revision
451
 
        :return: None
452
 
        """
453
 
        raise NotImplementedError(self.update_revisions)
454
 
 
 
994
        """Pull in new perfect-fit revisions."""
 
995
        from bzrlib.fetch import greedy_fetch
 
996
        from bzrlib.revision import get_intervening_revisions
 
997
        if stop_revision is None:
 
998
            stop_revision = other.last_revision()
 
999
        greedy_fetch(to_branch=self, from_branch=other,
 
1000
                     revision=stop_revision)
 
1001
        pullable_revs = self.missing_revisions(
 
1002
            other, other.revision_id_to_revno(stop_revision))
 
1003
        if pullable_revs:
 
1004
            greedy_fetch(to_branch=self,
 
1005
                         from_branch=other,
 
1006
                         revision=pullable_revs[-1])
 
1007
            self.append_revision(*pullable_revs)
 
1008
    
 
1009
 
 
1010
    def commit(self, *args, **kw):
 
1011
        from bzrlib.commit import Commit
 
1012
        Commit().commit(self, *args, **kw)
 
1013
    
455
1014
    def revision_id_to_revno(self, revision_id):
456
1015
        """Given a revision id, return its revno"""
457
1016
        if revision_id is None:
458
1017
            return 0
459
 
        revision_id = osutils.safe_revision_id(revision_id)
460
1018
        history = self.revision_history()
461
1019
        try:
462
1020
            return history.index(revision_id) + 1
469
1027
            return None
470
1028
        if history is None:
471
1029
            history = self.revision_history()
472
 
        if revno <= 0 or revno > len(history):
 
1030
        elif revno <= 0 or revno > len(history):
473
1031
            raise bzrlib.errors.NoSuchRevision(self, revno)
474
1032
        return history[revno - 1]
475
1033
 
476
 
    def pull(self, source, overwrite=False, stop_revision=None):
477
 
        """Mirror source into this branch.
478
 
 
479
 
        This branch is considered to be 'local', having low latency.
480
 
 
481
 
        :returns: PullResult instance
482
 
        """
483
 
        raise NotImplementedError(self.pull)
484
 
 
485
 
    def push(self, target, overwrite=False, stop_revision=None):
486
 
        """Mirror this branch into target.
487
 
 
488
 
        This branch is considered to be 'local', having low latency.
489
 
        """
490
 
        raise NotImplementedError(self.push)
 
1034
    def revision_tree(self, revision_id):
 
1035
        """Return Tree for a revision on this branch.
 
1036
 
 
1037
        `revision_id` may be None for the null revision, in which case
 
1038
        an `EmptyTree` is returned."""
 
1039
        # TODO: refactor this to use an existing revision object
 
1040
        # so we don't need to read it in twice.
 
1041
        if revision_id == None:
 
1042
            return EmptyTree()
 
1043
        else:
 
1044
            inv = self.get_revision_inventory(revision_id)
 
1045
            return RevisionTree(self.weave_store, inv, revision_id)
 
1046
 
 
1047
 
 
1048
    def working_tree(self):
 
1049
        """Return a `Tree` for the working copy."""
 
1050
        from bzrlib.workingtree import WorkingTree
 
1051
        # TODO: In the future, WorkingTree should utilize Transport
 
1052
        # RobertCollins 20051003 - I don't think it should - working trees are
 
1053
        # much more complex to keep consistent than our careful .bzr subset.
 
1054
        # instead, we should say that working trees are local only, and optimise
 
1055
        # for that.
 
1056
        return WorkingTree(self._transport.base, self.read_working_inventory())
 
1057
 
491
1058
 
492
1059
    def basis_tree(self):
493
 
        """Return `Tree` object for last revision."""
494
 
        return self.repository.revision_tree(self.last_revision())
 
1060
        """Return `Tree` object for last revision.
 
1061
 
 
1062
        If there are no revisions yet, return an `EmptyTree`.
 
1063
        """
 
1064
        return self.revision_tree(self.last_revision())
 
1065
 
495
1066
 
496
1067
    def rename_one(self, from_rel, to_rel):
497
1068
        """Rename one file.
498
1069
 
499
1070
        This can change the directory or the filename or both.
500
1071
        """
501
 
        raise NotImplementedError(self.rename_one)
 
1072
        self.lock_write()
 
1073
        try:
 
1074
            tree = self.working_tree()
 
1075
            inv = tree.inventory
 
1076
            if not tree.has_filename(from_rel):
 
1077
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
1078
            if tree.has_filename(to_rel):
 
1079
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
1080
 
 
1081
            file_id = inv.path2id(from_rel)
 
1082
            if file_id == None:
 
1083
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
1084
 
 
1085
            if inv.path2id(to_rel):
 
1086
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
1087
 
 
1088
            to_dir, to_tail = os.path.split(to_rel)
 
1089
            to_dir_id = inv.path2id(to_dir)
 
1090
            if to_dir_id == None and to_dir != '':
 
1091
                raise BzrError("can't determine destination directory id for %r" % to_dir)
 
1092
 
 
1093
            mutter("rename_one:")
 
1094
            mutter("  file_id    {%s}" % file_id)
 
1095
            mutter("  from_rel   %r" % from_rel)
 
1096
            mutter("  to_rel     %r" % to_rel)
 
1097
            mutter("  to_dir     %r" % to_dir)
 
1098
            mutter("  to_dir_id  {%s}" % to_dir_id)
 
1099
 
 
1100
            inv.rename(file_id, to_dir_id, to_tail)
 
1101
 
 
1102
            from_abs = self.abspath(from_rel)
 
1103
            to_abs = self.abspath(to_rel)
 
1104
            try:
 
1105
                rename(from_abs, to_abs)
 
1106
            except OSError, e:
 
1107
                raise BzrError("failed to rename %r to %r: %s"
 
1108
                        % (from_abs, to_abs, e[1]),
 
1109
                        ["rename rolled back"])
 
1110
 
 
1111
            self._write_inventory(inv)
 
1112
        finally:
 
1113
            self.unlock()
 
1114
 
502
1115
 
503
1116
    def move(self, from_paths, to_name):
504
1117
        """Rename files.
514
1127
        This returns a list of (from_path, to_path) pairs for each
515
1128
        entry that is moved.
516
1129
        """
517
 
        raise NotImplementedError(self.move)
 
1130
        result = []
 
1131
        self.lock_write()
 
1132
        try:
 
1133
            ## TODO: Option to move IDs only
 
1134
            assert not isinstance(from_paths, basestring)
 
1135
            tree = self.working_tree()
 
1136
            inv = tree.inventory
 
1137
            to_abs = self.abspath(to_name)
 
1138
            if not isdir(to_abs):
 
1139
                raise BzrError("destination %r is not a directory" % to_abs)
 
1140
            if not tree.has_filename(to_name):
 
1141
                raise BzrError("destination %r not in working directory" % to_abs)
 
1142
            to_dir_id = inv.path2id(to_name)
 
1143
            if to_dir_id == None and to_name != '':
 
1144
                raise BzrError("destination %r is not a versioned directory" % to_name)
 
1145
            to_dir_ie = inv[to_dir_id]
 
1146
            if to_dir_ie.kind not in ('directory', 'root_directory'):
 
1147
                raise BzrError("destination %r is not a directory" % to_abs)
 
1148
 
 
1149
            to_idpath = inv.get_idpath(to_dir_id)
 
1150
 
 
1151
            for f in from_paths:
 
1152
                if not tree.has_filename(f):
 
1153
                    raise BzrError("%r does not exist in working tree" % f)
 
1154
                f_id = inv.path2id(f)
 
1155
                if f_id == None:
 
1156
                    raise BzrError("%r is not versioned" % f)
 
1157
                name_tail = splitpath(f)[-1]
 
1158
                dest_path = appendpath(to_name, name_tail)
 
1159
                if tree.has_filename(dest_path):
 
1160
                    raise BzrError("destination %r already exists" % dest_path)
 
1161
                if f_id in to_idpath:
 
1162
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
 
1163
 
 
1164
            # OK, so there's a race here, it's possible that someone will
 
1165
            # create a file in this interval and then the rename might be
 
1166
            # left half-done.  But we should have caught most problems.
 
1167
 
 
1168
            for f in from_paths:
 
1169
                name_tail = splitpath(f)[-1]
 
1170
                dest_path = appendpath(to_name, name_tail)
 
1171
                result.append((f, dest_path))
 
1172
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
1173
                try:
 
1174
                    rename(self.abspath(f), self.abspath(dest_path))
 
1175
                except OSError, e:
 
1176
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
 
1177
                            ["rename rolled back"])
 
1178
 
 
1179
            self._write_inventory(inv)
 
1180
        finally:
 
1181
            self.unlock()
 
1182
 
 
1183
        return result
 
1184
 
 
1185
 
 
1186
    def revert(self, filenames, old_tree=None, backups=True):
 
1187
        """Restore selected files to the versions from a previous tree.
 
1188
 
 
1189
        backups
 
1190
            If true (default) backups are made of files before
 
1191
            they're renamed.
 
1192
        """
 
1193
        from bzrlib.errors import NotVersionedError, BzrError
 
1194
        from bzrlib.atomicfile import AtomicFile
 
1195
        from bzrlib.osutils import backup_file
 
1196
        
 
1197
        inv = self.read_working_inventory()
 
1198
        if old_tree is None:
 
1199
            old_tree = self.basis_tree()
 
1200
        old_inv = old_tree.inventory
 
1201
 
 
1202
        nids = []
 
1203
        for fn in filenames:
 
1204
            file_id = inv.path2id(fn)
 
1205
            if not file_id:
 
1206
                raise NotVersionedError("not a versioned file", fn)
 
1207
            if not old_inv.has_id(file_id):
 
1208
                raise BzrError("file not present in old tree", fn, file_id)
 
1209
            nids.append((fn, file_id))
 
1210
            
 
1211
        # TODO: Rename back if it was previously at a different location
 
1212
 
 
1213
        # TODO: If given a directory, restore the entire contents from
 
1214
        # the previous version.
 
1215
 
 
1216
        # TODO: Make a backup to a temporary file.
 
1217
 
 
1218
        # TODO: If the file previously didn't exist, delete it?
 
1219
        for fn, file_id in nids:
 
1220
            backup_file(fn)
 
1221
            
 
1222
            f = AtomicFile(fn, 'wb')
 
1223
            try:
 
1224
                f.write(old_tree.get_file(file_id).read())
 
1225
                f.commit()
 
1226
            finally:
 
1227
                f.close()
 
1228
 
 
1229
 
 
1230
    def pending_merges(self):
 
1231
        """Return a list of pending merges.
 
1232
 
 
1233
        These are revisions that have been merged into the working
 
1234
        directory but not yet committed.
 
1235
        """
 
1236
        cfn = self._rel_controlfilename('pending-merges')
 
1237
        if not self._transport.has(cfn):
 
1238
            return []
 
1239
        p = []
 
1240
        for l in self.controlfile('pending-merges', 'r').readlines():
 
1241
            p.append(l.rstrip('\n'))
 
1242
        return p
 
1243
 
 
1244
 
 
1245
    def add_pending_merge(self, *revision_ids):
 
1246
        # TODO: Perhaps should check at this point that the
 
1247
        # history of the revision is actually present?
 
1248
        p = self.pending_merges()
 
1249
        updated = False
 
1250
        for rev_id in revision_ids:
 
1251
            if rev_id in p:
 
1252
                continue
 
1253
            p.append(rev_id)
 
1254
            updated = True
 
1255
        if updated:
 
1256
            self.set_pending_merges(p)
 
1257
 
 
1258
    def set_pending_merges(self, rev_list):
 
1259
        self.lock_write()
 
1260
        try:
 
1261
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
 
1262
        finally:
 
1263
            self.unlock()
 
1264
 
518
1265
 
519
1266
    def get_parent(self):
520
1267
        """Return the parent location of the branch.
523
1270
        pattern is that the user can override it by specifying a
524
1271
        location.
525
1272
        """
526
 
        raise NotImplementedError(self.get_parent)
527
 
 
528
 
    def _set_config_location(self, name, url, config=None,
529
 
                             make_relative=False):
530
 
        if config is None:
531
 
            config = self.get_config()
532
 
        if url is None:
533
 
            url = ''
534
 
        elif make_relative:
535
 
            url = urlutils.relative_url(self.base, url)
536
 
        config.set_user_option(name, url)
537
 
 
538
 
    def _get_config_location(self, name, config=None):
539
 
        if config is None:
540
 
            config = self.get_config()
541
 
        location = config.get_user_option(name)
542
 
        if location == '':
543
 
            location = None
544
 
        return location
545
 
 
546
 
    def get_submit_branch(self):
547
 
        """Return the submit location of the branch.
548
 
 
549
 
        This is the default location for bundle.  The usual
550
 
        pattern is that the user can override it by specifying a
551
 
        location.
552
 
        """
553
 
        return self.get_config().get_user_option('submit_branch')
554
 
 
555
 
    def set_submit_branch(self, location):
556
 
        """Return the submit location of the branch.
557
 
 
558
 
        This is the default location for bundle.  The usual
559
 
        pattern is that the user can override it by specifying a
560
 
        location.
561
 
        """
562
 
        self.get_config().set_user_option('submit_branch', location)
563
 
 
564
 
    def get_public_branch(self):
565
 
        """Return the public location of the branch.
566
 
 
567
 
        This is is used by merge directives.
568
 
        """
569
 
        return self._get_config_location('public_branch')
570
 
 
571
 
    def set_public_branch(self, location):
572
 
        """Return the submit location of the branch.
573
 
 
574
 
        This is the default location for bundle.  The usual
575
 
        pattern is that the user can override it by specifying a
576
 
        location.
577
 
        """
578
 
        self._set_config_location('public_branch', location)
579
 
 
580
 
    def get_push_location(self):
581
 
        """Return the None or the location to push this branch to."""
582
 
        raise NotImplementedError(self.get_push_location)
583
 
 
584
 
    def set_push_location(self, location):
585
 
        """Set a new push location for this branch."""
586
 
        raise NotImplementedError(self.set_push_location)
 
1273
        import errno
 
1274
        _locs = ['parent', 'pull', 'x-pull']
 
1275
        for l in _locs:
 
1276
            try:
 
1277
                return self.controlfile(l, 'r').read().strip('\n')
 
1278
            except IOError, e:
 
1279
                if e.errno != errno.ENOENT:
 
1280
                    raise
 
1281
        return None
 
1282
 
587
1283
 
588
1284
    def set_parent(self, url):
589
 
        raise NotImplementedError(self.set_parent)
590
 
 
591
 
    @needs_write_lock
592
 
    def update(self):
593
 
        """Synchronise this branch with the master branch if any. 
594
 
 
595
 
        :return: None or the last_revision pivoted out during the update.
596
 
        """
597
 
        return None
 
1285
        # TODO: Maybe delete old location files?
 
1286
        from bzrlib.atomicfile import AtomicFile
 
1287
        self.lock_write()
 
1288
        try:
 
1289
            f = AtomicFile(self.controlfilename('parent'))
 
1290
            try:
 
1291
                f.write(url + '\n')
 
1292
                f.commit()
 
1293
            finally:
 
1294
                f.close()
 
1295
        finally:
 
1296
            self.unlock()
598
1297
 
599
1298
    def check_revno(self, revno):
600
1299
        """\
611
1310
        """
612
1311
        if revno < 1 or revno > self.revno():
613
1312
            raise InvalidRevisionNumber(revno)
614
 
 
615
 
    @needs_read_lock
616
 
    def clone(self, to_bzrdir, revision_id=None):
617
 
        """Clone this branch into to_bzrdir preserving all semantic values.
618
 
        
619
 
        revision_id: if not None, the revision history in the new branch will
620
 
                     be truncated to end with revision_id.
621
 
        """
622
 
        result = self._format.initialize(to_bzrdir)
623
 
        self.copy_content_into(result, revision_id=revision_id)
624
 
        return  result
625
 
 
626
 
    @needs_read_lock
627
 
    def sprout(self, to_bzrdir, revision_id=None):
628
 
        """Create a new line of development from the branch, into to_bzrdir.
629
 
        
630
 
        revision_id: if not None, the revision history in the new branch will
631
 
                     be truncated to end with revision_id.
632
 
        """
633
 
        result = self._format.initialize(to_bzrdir)
634
 
        self.copy_content_into(result, revision_id=revision_id)
635
 
        result.set_parent(self.bzrdir.root_transport.base)
636
 
        return result
637
 
 
638
 
    def _synchronize_history(self, destination, revision_id):
639
 
        """Synchronize last revision and revision history between branches.
640
 
 
641
 
        This version is most efficient when the destination is also a
642
 
        BzrBranch5, but works for BzrBranch6 as long as the revision
643
 
        history is the true lefthand parent history, and all of the revisions
644
 
        are in the destination's repository.  If not, set_revision_history
645
 
        will fail.
646
 
 
647
 
        :param destination: The branch to copy the history into
648
 
        :param revision_id: The revision-id to truncate history at.  May
649
 
          be None to copy complete history.
650
 
        """
651
 
        new_history = self.revision_history()
652
 
        if revision_id is not None:
653
 
            revision_id = osutils.safe_revision_id(revision_id)
654
 
            try:
655
 
                new_history = new_history[:new_history.index(revision_id) + 1]
656
 
            except ValueError:
657
 
                rev = self.repository.get_revision(revision_id)
658
 
                new_history = rev.get_history(self.repository)[1:]
659
 
        destination.set_revision_history(new_history)
660
 
 
661
 
    @needs_read_lock
662
 
    def copy_content_into(self, destination, revision_id=None):
663
 
        """Copy the content of self into destination.
664
 
 
665
 
        revision_id: if not None, the revision history in the new branch will
666
 
                     be truncated to end with revision_id.
667
 
        """
668
 
        self._synchronize_history(destination, revision_id)
669
 
        try:
670
 
            parent = self.get_parent()
671
 
        except errors.InaccessibleParent, e:
672
 
            mutter('parent was not accessible to copy: %s', e)
673
 
        else:
674
 
            if parent:
675
 
                destination.set_parent(parent)
676
 
        self.tags.merge_to(destination.tags)
677
 
 
678
 
    @needs_read_lock
679
 
    def check(self):
680
 
        """Check consistency of the branch.
681
 
 
682
 
        In particular this checks that revisions given in the revision-history
683
 
        do actually match up in the revision graph, and that they're all 
684
 
        present in the repository.
685
 
        
686
 
        Callers will typically also want to check the repository.
687
 
 
688
 
        :return: A BranchCheckResult.
689
 
        """
690
 
        mainline_parent_id = None
691
 
        for revision_id in self.revision_history():
692
 
            try:
693
 
                revision = self.repository.get_revision(revision_id)
694
 
            except errors.NoSuchRevision, e:
695
 
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
696
 
                            % revision_id)
697
 
            # In general the first entry on the revision history has no parents.
698
 
            # But it's not illegal for it to have parents listed; this can happen
699
 
            # in imports from Arch when the parents weren't reachable.
700
 
            if mainline_parent_id is not None:
701
 
                if mainline_parent_id not in revision.parent_ids:
702
 
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
703
 
                                        "parents of {%s}"
704
 
                                        % (mainline_parent_id, revision_id))
705
 
            mainline_parent_id = revision_id
706
 
        return BranchCheckResult(self)
707
 
 
708
 
    def _get_checkout_format(self):
709
 
        """Return the most suitable metadir for a checkout of this branch.
710
 
        Weaves are used if this branch's repostory uses weaves.
711
 
        """
712
 
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
713
 
            from bzrlib.repofmt import weaverepo
714
 
            format = bzrdir.BzrDirMetaFormat1()
715
 
            format.repository_format = weaverepo.RepositoryFormat7()
716
 
        else:
717
 
            format = self.repository.bzrdir.checkout_metadir()
718
 
            format.set_branch_format(self._format)
719
 
        return format
720
 
 
721
 
    def create_checkout(self, to_location, revision_id=None,
722
 
                        lightweight=False):
723
 
        """Create a checkout of a branch.
724
 
        
725
 
        :param to_location: The url to produce the checkout at
726
 
        :param revision_id: The revision to check out
727
 
        :param lightweight: If True, produce a lightweight checkout, otherwise,
728
 
        produce a bound branch (heavyweight checkout)
729
 
        :return: The tree of the created checkout
730
 
        """
731
 
        t = transport.get_transport(to_location)
732
 
        try:
733
 
            t.mkdir('.')
734
 
        except errors.FileExists:
735
 
            pass
736
 
        if lightweight:
737
 
            format = self._get_checkout_format()
738
 
            checkout = format.initialize_on_transport(t)
739
 
            BranchReferenceFormat().initialize(checkout, self)
740
 
        else:
741
 
            format = self._get_checkout_format()
742
 
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
743
 
                to_location, force_new_tree=False, format=format)
744
 
            checkout = checkout_branch.bzrdir
745
 
            checkout_branch.bind(self)
746
 
            # pull up to the specified revision_id to set the initial 
747
 
            # branch tip correctly, and seed it with history.
748
 
            checkout_branch.pull(self, stop_revision=revision_id)
749
 
        tree = checkout.create_workingtree(revision_id)
750
 
        basis_tree = tree.basis_tree()
751
 
        basis_tree.lock_read()
752
 
        try:
753
 
            for path, file_id in basis_tree.iter_references():
754
 
                reference_parent = self.reference_parent(file_id, path)
755
 
                reference_parent.create_checkout(tree.abspath(path),
756
 
                    basis_tree.get_reference_revision(file_id, path),
757
 
                    lightweight)
758
 
        finally:
759
 
            basis_tree.unlock()
760
 
        return tree
761
 
 
762
 
    def reference_parent(self, file_id, path):
763
 
        """Return the parent branch for a tree-reference file_id
764
 
        :param file_id: The file_id of the tree reference
765
 
        :param path: The path of the file_id in the tree
766
 
        :return: A branch associated with the file_id
767
 
        """
768
 
        # FIXME should provide multiple branches, based on config
769
 
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
770
 
 
771
 
    def supports_tags(self):
772
 
        return self._format.supports_tags()
773
 
 
774
 
 
775
 
class BranchFormat(object):
776
 
    """An encapsulation of the initialization and open routines for a format.
777
 
 
778
 
    Formats provide three things:
779
 
     * An initialization routine,
780
 
     * a format string,
781
 
     * an open routine.
782
 
 
783
 
    Formats are placed in an dict by their format string for reference 
784
 
    during branch opening. Its not required that these be instances, they
785
 
    can be classes themselves with class methods - it simply depends on 
786
 
    whether state is needed for a given format or not.
787
 
 
788
 
    Once a format is deprecated, just deprecate the initialize and open
789
 
    methods on the format class. Do not deprecate the object, as the 
790
 
    object will be created every time regardless.
791
 
    """
792
 
 
793
 
    _default_format = None
794
 
    """The default format used for new branches."""
795
 
 
796
 
    _formats = {}
797
 
    """The known formats."""
798
 
 
799
 
    @classmethod
800
 
    def find_format(klass, a_bzrdir):
801
 
        """Return the format for the branch object in a_bzrdir."""
802
 
        try:
803
 
            transport = a_bzrdir.get_branch_transport(None)
804
 
            format_string = transport.get("format").read()
805
 
            return klass._formats[format_string]
806
 
        except NoSuchFile:
807
 
            raise NotBranchError(path=transport.base)
808
 
        except KeyError:
809
 
            raise errors.UnknownFormatError(format=format_string)
810
 
 
811
 
    @classmethod
812
 
    def get_default_format(klass):
813
 
        """Return the current default format."""
814
 
        return klass._default_format
815
 
 
816
 
    def get_format_string(self):
817
 
        """Return the ASCII format string that identifies this format."""
818
 
        raise NotImplementedError(self.get_format_string)
819
 
 
820
 
    def get_format_description(self):
821
 
        """Return the short format description for this format."""
822
 
        raise NotImplementedError(self.get_format_description)
823
 
 
824
 
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
825
 
                           set_format=True):
826
 
        """Initialize a branch in a bzrdir, with specified files
827
 
 
828
 
        :param a_bzrdir: The bzrdir to initialize the branch in
829
 
        :param utf8_files: The files to create as a list of
830
 
            (filename, content) tuples
831
 
        :param set_format: If True, set the format with
832
 
            self.get_format_string.  (BzrBranch4 has its format set
833
 
            elsewhere)
834
 
        :return: a branch in this format
835
 
        """
836
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
837
 
        branch_transport = a_bzrdir.get_branch_transport(self)
838
 
        lock_map = {
839
 
            'metadir': ('lock', lockdir.LockDir),
840
 
            'branch4': ('branch-lock', lockable_files.TransportLock),
841
 
        }
842
 
        lock_name, lock_class = lock_map[lock_type]
843
 
        control_files = lockable_files.LockableFiles(branch_transport,
844
 
            lock_name, lock_class)
845
 
        control_files.create_lock()
846
 
        control_files.lock_write()
847
 
        if set_format:
848
 
            control_files.put_utf8('format', self.get_format_string())
849
 
        try:
850
 
            for file, content in utf8_files:
851
 
                control_files.put_utf8(file, content)
852
 
        finally:
853
 
            control_files.unlock()
854
 
        return self.open(a_bzrdir, _found=True)
855
 
 
856
 
    def initialize(self, a_bzrdir):
857
 
        """Create a branch of this format in a_bzrdir."""
858
 
        raise NotImplementedError(self.initialize)
859
 
 
860
 
    def is_supported(self):
861
 
        """Is this format supported?
862
 
 
863
 
        Supported formats can be initialized and opened.
864
 
        Unsupported formats may not support initialization or committing or 
865
 
        some other features depending on the reason for not being supported.
866
 
        """
867
 
        return True
868
 
 
869
 
    def open(self, a_bzrdir, _found=False):
870
 
        """Return the branch object for a_bzrdir
871
 
 
872
 
        _found is a private parameter, do not use it. It is used to indicate
873
 
               if format probing has already be done.
874
 
        """
875
 
        raise NotImplementedError(self.open)
876
 
 
877
 
    @classmethod
878
 
    def register_format(klass, format):
879
 
        klass._formats[format.get_format_string()] = format
880
 
 
881
 
    @classmethod
882
 
    def set_default_format(klass, format):
883
 
        klass._default_format = format
884
 
 
885
 
    @classmethod
886
 
    def unregister_format(klass, format):
887
 
        assert klass._formats[format.get_format_string()] is format
888
 
        del klass._formats[format.get_format_string()]
889
 
 
890
 
    def __str__(self):
891
 
        return self.get_format_string().rstrip()
892
 
 
893
 
    def supports_tags(self):
894
 
        """True if this format supports tags stored in the branch"""
895
 
        return False  # by default
896
 
 
897
 
    # XXX: Probably doesn't really belong here -- mbp 20070212
898
 
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
899
 
            lock_class):
900
 
        branch_transport = a_bzrdir.get_branch_transport(self)
901
 
        control_files = lockable_files.LockableFiles(branch_transport,
902
 
            lock_filename, lock_class)
903
 
        control_files.create_lock()
904
 
        control_files.lock_write()
905
 
        try:
906
 
            for filename, content in utf8_files:
907
 
                control_files.put_utf8(filename, content)
908
 
        finally:
909
 
            control_files.unlock()
910
 
 
911
 
 
912
 
class BranchHooks(Hooks):
913
 
    """A dictionary mapping hook name to a list of callables for branch hooks.
914
 
    
915
 
    e.g. ['set_rh'] Is the list of items to be called when the
916
 
    set_revision_history function is invoked.
917
 
    """
918
 
 
919
 
    def __init__(self):
920
 
        """Create the default hooks.
921
 
 
922
 
        These are all empty initially, because by default nothing should get
923
 
        notified.
924
 
        """
925
 
        Hooks.__init__(self)
926
 
        # Introduced in 0.15:
927
 
        # invoked whenever the revision history has been set
928
 
        # with set_revision_history. The api signature is
929
 
        # (branch, revision_history), and the branch will
930
 
        # be write-locked.
931
 
        self['set_rh'] = []
932
 
        # invoked after a push operation completes.
933
 
        # the api signature is
934
 
        # (push_result)
935
 
        # containing the members
936
 
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
937
 
        # where local is the local branch or None, master is the target 
938
 
        # master branch, and the rest should be self explanatory. The source
939
 
        # is read locked and the target branches write locked. Source will
940
 
        # be the local low-latency branch.
941
 
        self['post_push'] = []
942
 
        # invoked after a pull operation completes.
943
 
        # the api signature is
944
 
        # (pull_result)
945
 
        # containing the members
946
 
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
947
 
        # where local is the local branch or None, master is the target 
948
 
        # master branch, and the rest should be self explanatory. The source
949
 
        # is read locked and the target branches write locked. The local
950
 
        # branch is the low-latency branch.
951
 
        self['post_pull'] = []
952
 
        # invoked after a commit operation completes.
953
 
        # the api signature is 
954
 
        # (local, master, old_revno, old_revid, new_revno, new_revid)
955
 
        # old_revid is NULL_REVISION for the first commit to a branch.
956
 
        self['post_commit'] = []
957
 
        # invoked after a uncommit operation completes.
958
 
        # the api signature is
959
 
        # (local, master, old_revno, old_revid, new_revno, new_revid) where
960
 
        # local is the local branch or None, master is the target branch,
961
 
        # and an empty branch recieves new_revno of 0, new_revid of None.
962
 
        self['post_uncommit'] = []
963
 
 
964
 
 
965
 
# install the default hooks into the Branch class.
966
 
Branch.hooks = BranchHooks()
967
 
 
968
 
 
969
 
class BzrBranchFormat4(BranchFormat):
970
 
    """Bzr branch format 4.
971
 
 
972
 
    This format has:
973
 
     - a revision-history file.
974
 
     - a branch-lock lock file [ to be shared with the bzrdir ]
975
 
    """
976
 
 
977
 
    def get_format_description(self):
978
 
        """See BranchFormat.get_format_description()."""
979
 
        return "Branch format 4"
980
 
 
981
 
    def initialize(self, a_bzrdir):
982
 
        """Create a branch of this format in a_bzrdir."""
983
 
        utf8_files = [('revision-history', ''),
984
 
                      ('branch-name', ''),
985
 
                      ]
986
 
        return self._initialize_helper(a_bzrdir, utf8_files,
987
 
                                       lock_type='branch4', set_format=False)
988
 
 
989
 
    def __init__(self):
990
 
        super(BzrBranchFormat4, self).__init__()
991
 
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
992
 
 
993
 
    def open(self, a_bzrdir, _found=False):
994
 
        """Return the branch object for a_bzrdir
995
 
 
996
 
        _found is a private parameter, do not use it. It is used to indicate
997
 
               if format probing has already be done.
998
 
        """
999
 
        if not _found:
1000
 
            # we are being called directly and must probe.
1001
 
            raise NotImplementedError
1002
 
        return BzrBranch(_format=self,
1003
 
                         _control_files=a_bzrdir._control_files,
1004
 
                         a_bzrdir=a_bzrdir,
1005
 
                         _repository=a_bzrdir.open_repository())
1006
 
 
1007
 
    def __str__(self):
1008
 
        return "Bazaar-NG branch format 4"
1009
 
 
1010
 
 
1011
 
class BzrBranchFormat5(BranchFormat):
1012
 
    """Bzr branch format 5.
1013
 
 
1014
 
    This format has:
1015
 
     - a revision-history file.
1016
 
     - a format string
1017
 
     - a lock dir guarding the branch itself
1018
 
     - all of this stored in a branch/ subdirectory
1019
 
     - works with shared repositories.
1020
 
 
1021
 
    This format is new in bzr 0.8.
1022
 
    """
1023
 
 
1024
 
    def get_format_string(self):
1025
 
        """See BranchFormat.get_format_string()."""
1026
 
        return "Bazaar-NG branch format 5\n"
1027
 
 
1028
 
    def get_format_description(self):
1029
 
        """See BranchFormat.get_format_description()."""
1030
 
        return "Branch format 5"
1031
 
        
1032
 
    def initialize(self, a_bzrdir):
1033
 
        """Create a branch of this format in a_bzrdir."""
1034
 
        utf8_files = [('revision-history', ''),
1035
 
                      ('branch-name', ''),
1036
 
                      ]
1037
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1038
 
 
1039
 
    def __init__(self):
1040
 
        super(BzrBranchFormat5, self).__init__()
1041
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1042
 
 
1043
 
    def open(self, a_bzrdir, _found=False):
1044
 
        """Return the branch object for a_bzrdir
1045
 
 
1046
 
        _found is a private parameter, do not use it. It is used to indicate
1047
 
               if format probing has already be done.
1048
 
        """
1049
 
        if not _found:
1050
 
            format = BranchFormat.find_format(a_bzrdir)
1051
 
            assert format.__class__ == self.__class__
1052
 
        transport = a_bzrdir.get_branch_transport(None)
1053
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1054
 
                                                     lockdir.LockDir)
1055
 
        return BzrBranch5(_format=self,
1056
 
                          _control_files=control_files,
1057
 
                          a_bzrdir=a_bzrdir,
1058
 
                          _repository=a_bzrdir.find_repository())
1059
 
 
1060
 
 
1061
 
class BzrBranchFormat6(BzrBranchFormat5):
1062
 
    """Branch format with last-revision
1063
 
 
1064
 
    Unlike previous formats, this has no explicit revision history. Instead,
1065
 
    this just stores the last-revision, and the left-hand history leading
1066
 
    up to there is the history.
1067
 
 
1068
 
    This format was introduced in bzr 0.15
1069
 
    """
1070
 
 
1071
 
    def get_format_string(self):
1072
 
        """See BranchFormat.get_format_string()."""
1073
 
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1074
 
 
1075
 
    def get_format_description(self):
1076
 
        """See BranchFormat.get_format_description()."""
1077
 
        return "Branch format 6"
1078
 
 
1079
 
    def initialize(self, a_bzrdir):
1080
 
        """Create a branch of this format in a_bzrdir."""
1081
 
        utf8_files = [('last-revision', '0 null:\n'),
1082
 
                      ('branch-name', ''),
1083
 
                      ('branch.conf', ''),
1084
 
                      ('tags', ''),
1085
 
                      ]
1086
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1087
 
 
1088
 
    def open(self, a_bzrdir, _found=False):
1089
 
        """Return the branch object for a_bzrdir
1090
 
 
1091
 
        _found is a private parameter, do not use it. It is used to indicate
1092
 
               if format probing has already be done.
1093
 
        """
1094
 
        if not _found:
1095
 
            format = BranchFormat.find_format(a_bzrdir)
1096
 
            assert format.__class__ == self.__class__
1097
 
        transport = a_bzrdir.get_branch_transport(None)
1098
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1099
 
                                                     lockdir.LockDir)
1100
 
        return BzrBranch6(_format=self,
1101
 
                          _control_files=control_files,
1102
 
                          a_bzrdir=a_bzrdir,
1103
 
                          _repository=a_bzrdir.find_repository())
1104
 
 
1105
 
    def supports_tags(self):
1106
 
        return True
1107
 
 
1108
 
 
1109
 
class BranchReferenceFormat(BranchFormat):
1110
 
    """Bzr branch reference format.
1111
 
 
1112
 
    Branch references are used in implementing checkouts, they
1113
 
    act as an alias to the real branch which is at some other url.
1114
 
 
1115
 
    This format has:
1116
 
     - A location file
1117
 
     - a format string
1118
 
    """
1119
 
 
1120
 
    def get_format_string(self):
1121
 
        """See BranchFormat.get_format_string()."""
1122
 
        return "Bazaar-NG Branch Reference Format 1\n"
1123
 
 
1124
 
    def get_format_description(self):
1125
 
        """See BranchFormat.get_format_description()."""
1126
 
        return "Checkout reference format 1"
1127
 
        
1128
 
    def initialize(self, a_bzrdir, target_branch=None):
1129
 
        """Create a branch of this format in a_bzrdir."""
1130
 
        if target_branch is None:
1131
 
            # this format does not implement branch itself, thus the implicit
1132
 
            # creation contract must see it as uninitializable
1133
 
            raise errors.UninitializableFormat(self)
1134
 
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
1135
 
        branch_transport = a_bzrdir.get_branch_transport(self)
1136
 
        branch_transport.put_bytes('location',
1137
 
            target_branch.bzrdir.root_transport.base)
1138
 
        branch_transport.put_bytes('format', self.get_format_string())
1139
 
        return self.open(a_bzrdir, _found=True)
1140
 
 
1141
 
    def __init__(self):
1142
 
        super(BranchReferenceFormat, self).__init__()
1143
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1144
 
 
1145
 
    def _make_reference_clone_function(format, a_branch):
1146
 
        """Create a clone() routine for a branch dynamically."""
1147
 
        def clone(to_bzrdir, revision_id=None):
1148
 
            """See Branch.clone()."""
1149
 
            return format.initialize(to_bzrdir, a_branch)
1150
 
            # cannot obey revision_id limits when cloning a reference ...
1151
 
            # FIXME RBC 20060210 either nuke revision_id for clone, or
1152
 
            # emit some sort of warning/error to the caller ?!
1153
 
        return clone
1154
 
 
1155
 
    def open(self, a_bzrdir, _found=False):
1156
 
        """Return the branch that the branch reference in a_bzrdir points at.
1157
 
 
1158
 
        _found is a private parameter, do not use it. It is used to indicate
1159
 
               if format probing has already be done.
1160
 
        """
1161
 
        if not _found:
1162
 
            format = BranchFormat.find_format(a_bzrdir)
1163
 
            assert format.__class__ == self.__class__
1164
 
        transport = a_bzrdir.get_branch_transport(None)
1165
 
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
1166
 
        result = real_bzrdir.open_branch()
1167
 
        # this changes the behaviour of result.clone to create a new reference
1168
 
        # rather than a copy of the content of the branch.
1169
 
        # I did not use a proxy object because that needs much more extensive
1170
 
        # testing, and we are only changing one behaviour at the moment.
1171
 
        # If we decide to alter more behaviours - i.e. the implicit nickname
1172
 
        # then this should be refactored to introduce a tested proxy branch
1173
 
        # and a subclass of that for use in overriding clone() and ....
1174
 
        # - RBC 20060210
1175
 
        result.clone = self._make_reference_clone_function(result)
1176
 
        return result
1177
 
 
1178
 
 
1179
 
# formats which have no format string are not discoverable
1180
 
# and not independently creatable, so are not registered.
1181
 
__default_format = BzrBranchFormat5()
1182
 
BranchFormat.register_format(__default_format)
1183
 
BranchFormat.register_format(BranchReferenceFormat())
1184
 
BranchFormat.register_format(BzrBranchFormat6())
1185
 
BranchFormat.set_default_format(__default_format)
1186
 
_legacy_formats = [BzrBranchFormat4(),
1187
 
                   ]
1188
 
 
1189
 
class BzrBranch(Branch):
1190
 
    """A branch stored in the actual filesystem.
1191
 
 
1192
 
    Note that it's "local" in the context of the filesystem; it doesn't
1193
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1194
 
    it's writable, and can be accessed via the normal filesystem API.
1195
 
    """
1196
 
    
1197
 
    def __init__(self, _format=None,
1198
 
                 _control_files=None, a_bzrdir=None, _repository=None):
1199
 
        """Create new branch object at a particular location."""
1200
 
        Branch.__init__(self)
1201
 
        if a_bzrdir is None:
1202
 
            raise ValueError('a_bzrdir must be supplied')
1203
 
        else:
1204
 
            self.bzrdir = a_bzrdir
1205
 
        # self._transport used to point to the directory containing the
1206
 
        # control directory, but was not used - now it's just the transport
1207
 
        # for the branch control files.  mbp 20070212
1208
 
        self._base = self.bzrdir.transport.clone('..').base
1209
 
        self._format = _format
1210
 
        if _control_files is None:
1211
 
            raise ValueError('BzrBranch _control_files is None')
1212
 
        self.control_files = _control_files
1213
 
        self._transport = _control_files._transport
1214
 
        self.repository = _repository
1215
 
 
1216
 
    def __str__(self):
1217
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
1218
 
 
1219
 
    __repr__ = __str__
1220
 
 
1221
 
    def _get_base(self):
1222
 
        """Returns the directory containing the control directory."""
1223
 
        return self._base
1224
 
 
1225
 
    base = property(_get_base, doc="The URL for the root of this branch.")
1226
 
 
1227
 
    def abspath(self, name):
1228
 
        """See Branch.abspath."""
1229
 
        return self.control_files._transport.abspath(name)
1230
 
 
1231
 
 
1232
 
    @deprecated_method(zero_sixteen)
1233
 
    @needs_read_lock
1234
 
    def get_root_id(self):
1235
 
        """See Branch.get_root_id."""
1236
 
        tree = self.repository.revision_tree(self.last_revision())
1237
 
        return tree.inventory.root.file_id
1238
 
 
1239
 
    def is_locked(self):
1240
 
        return self.control_files.is_locked()
1241
 
 
1242
 
    def lock_write(self, token=None):
1243
 
        repo_token = self.repository.lock_write()
1244
 
        try:
1245
 
            token = self.control_files.lock_write(token=token)
1246
 
        except:
1247
 
            self.repository.unlock()
1248
 
            raise
1249
 
        return token
1250
 
 
1251
 
    def lock_read(self):
1252
 
        self.repository.lock_read()
1253
 
        try:
1254
 
            self.control_files.lock_read()
1255
 
        except:
1256
 
            self.repository.unlock()
1257
 
            raise
1258
 
 
1259
 
    def unlock(self):
1260
 
        # TODO: test for failed two phase locks. This is known broken.
1261
 
        try:
1262
 
            self.control_files.unlock()
1263
 
        finally:
1264
 
            self.repository.unlock()
1265
 
        if not self.control_files.is_locked():
1266
 
            # we just released the lock
1267
 
            self._clear_cached_state()
1268
 
        
1269
 
    def peek_lock_mode(self):
1270
 
        if self.control_files._lock_count == 0:
1271
 
            return None
1272
 
        else:
1273
 
            return self.control_files._lock_mode
1274
 
 
1275
 
    def get_physical_lock_status(self):
1276
 
        return self.control_files.get_physical_lock_status()
1277
 
 
1278
 
    @needs_read_lock
1279
 
    def print_file(self, file, revision_id):
1280
 
        """See Branch.print_file."""
1281
 
        return self.repository.print_file(file, revision_id)
1282
 
 
1283
 
    @needs_write_lock
1284
 
    def append_revision(self, *revision_ids):
1285
 
        """See Branch.append_revision."""
1286
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1287
 
        for revision_id in revision_ids:
1288
 
            _mod_revision.check_not_reserved_id(revision_id)
1289
 
            mutter("add {%s} to revision-history" % revision_id)
1290
 
        rev_history = self.revision_history()
1291
 
        rev_history.extend(revision_ids)
1292
 
        self.set_revision_history(rev_history)
1293
 
 
1294
 
    def _write_revision_history(self, history):
1295
 
        """Factored out of set_revision_history.
1296
 
 
1297
 
        This performs the actual writing to disk.
1298
 
        It is intended to be called by BzrBranch5.set_revision_history."""
1299
 
        self.control_files.put_bytes(
1300
 
            'revision-history', '\n'.join(history))
1301
 
 
1302
 
    @needs_write_lock
1303
 
    def set_revision_history(self, rev_history):
1304
 
        """See Branch.set_revision_history."""
1305
 
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1306
 
        self._write_revision_history(rev_history)
1307
 
        self._cache_revision_history(rev_history)
1308
 
        for hook in Branch.hooks['set_rh']:
1309
 
            hook(self, rev_history)
1310
 
 
1311
 
    @needs_write_lock
1312
 
    def set_last_revision_info(self, revno, revision_id):
1313
 
        revision_id = osutils.safe_revision_id(revision_id)
1314
 
        history = self._lefthand_history(revision_id)
1315
 
        assert len(history) == revno, '%d != %d' % (len(history), revno)
1316
 
        self.set_revision_history(history)
1317
 
 
1318
 
    def _gen_revision_history(self):
1319
 
        history = self.control_files.get('revision-history').read().split('\n')
1320
 
        if history[-1:] == ['']:
1321
 
            # There shouldn't be a trailing newline, but just in case.
1322
 
            history.pop()
1323
 
        return history
1324
 
 
1325
 
    def _lefthand_history(self, revision_id, last_rev=None,
1326
 
                          other_branch=None):
1327
 
        # stop_revision must be a descendant of last_revision
1328
 
        stop_graph = self.repository.get_revision_graph(revision_id)
1329
 
        if last_rev is not None and last_rev not in stop_graph:
1330
 
            # our previous tip is not merged into stop_revision
1331
 
            raise errors.DivergedBranches(self, other_branch)
1332
 
        # make a new revision history from the graph
1333
 
        current_rev_id = revision_id
1334
 
        new_history = []
1335
 
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1336
 
            new_history.append(current_rev_id)
1337
 
            current_rev_id_parents = stop_graph[current_rev_id]
1338
 
            try:
1339
 
                current_rev_id = current_rev_id_parents[0]
1340
 
            except IndexError:
1341
 
                current_rev_id = None
1342
 
        new_history.reverse()
1343
 
        return new_history
1344
 
 
1345
 
    @needs_write_lock
1346
 
    def generate_revision_history(self, revision_id, last_rev=None,
1347
 
        other_branch=None):
1348
 
        """Create a new revision history that will finish with revision_id.
1349
 
 
1350
 
        :param revision_id: the new tip to use.
1351
 
        :param last_rev: The previous last_revision. If not None, then this
1352
 
            must be a ancestory of revision_id, or DivergedBranches is raised.
1353
 
        :param other_branch: The other branch that DivergedBranches should
1354
 
            raise with respect to.
1355
 
        """
1356
 
        revision_id = osutils.safe_revision_id(revision_id)
1357
 
        self.set_revision_history(self._lefthand_history(revision_id,
1358
 
            last_rev, other_branch))
1359
 
 
1360
 
    @needs_write_lock
1361
 
    def update_revisions(self, other, stop_revision=None):
1362
 
        """See Branch.update_revisions."""
1363
 
        other.lock_read()
1364
 
        try:
1365
 
            if stop_revision is None:
1366
 
                stop_revision = other.last_revision()
1367
 
                if stop_revision is None:
1368
 
                    # if there are no commits, we're done.
1369
 
                    return
1370
 
            else:
1371
 
                stop_revision = osutils.safe_revision_id(stop_revision)
1372
 
            # whats the current last revision, before we fetch [and change it
1373
 
            # possibly]
1374
 
            last_rev = self.last_revision()
1375
 
            # we fetch here regardless of whether we need to so that we pickup
1376
 
            # filled in ghosts.
1377
 
            self.fetch(other, stop_revision)
1378
 
            my_ancestry = self.repository.get_ancestry(last_rev)
1379
 
            if stop_revision in my_ancestry:
1380
 
                # last_revision is a descendant of stop_revision
1381
 
                return
1382
 
            self.generate_revision_history(stop_revision, last_rev=last_rev,
1383
 
                other_branch=other)
1384
 
        finally:
1385
 
            other.unlock()
1386
 
 
1387
 
    def basis_tree(self):
1388
 
        """See Branch.basis_tree."""
1389
 
        return self.repository.revision_tree(self.last_revision())
1390
 
 
1391
 
    @deprecated_method(zero_eight)
1392
 
    def working_tree(self):
1393
 
        """Create a Working tree object for this branch."""
1394
 
 
1395
 
        from bzrlib.transport.local import LocalTransport
1396
 
        if (self.base.find('://') != -1 or 
1397
 
            not isinstance(self._transport, LocalTransport)):
1398
 
            raise NoWorkingTree(self.base)
1399
 
        return self.bzrdir.open_workingtree()
1400
 
 
1401
 
    @needs_write_lock
1402
 
    def pull(self, source, overwrite=False, stop_revision=None,
1403
 
        _hook_master=None, _run_hooks=True):
1404
 
        """See Branch.pull.
1405
 
 
1406
 
        :param _hook_master: Private parameter - set the branch to 
1407
 
            be supplied as the master to push hooks.
1408
 
        :param _run_hooks: Private parameter - allow disabling of
1409
 
            hooks, used when pushing to a master branch.
1410
 
        """
1411
 
        result = PullResult()
1412
 
        result.source_branch = source
1413
 
        result.target_branch = self
1414
 
        source.lock_read()
1415
 
        try:
1416
 
            result.old_revno, result.old_revid = self.last_revision_info()
1417
 
            try:
1418
 
                self.update_revisions(source, stop_revision)
1419
 
            except DivergedBranches:
1420
 
                if not overwrite:
1421
 
                    raise
1422
 
            if overwrite:
1423
 
                if stop_revision is None:
1424
 
                    stop_revision = source.last_revision()
1425
 
                self.generate_revision_history(stop_revision)
1426
 
            result.tag_conflicts = source.tags.merge_to(self.tags)
1427
 
            result.new_revno, result.new_revid = self.last_revision_info()
1428
 
            if _hook_master:
1429
 
                result.master_branch = _hook_master
1430
 
                result.local_branch = self
1431
 
            else:
1432
 
                result.master_branch = self
1433
 
                result.local_branch = None
1434
 
            if _run_hooks:
1435
 
                for hook in Branch.hooks['post_pull']:
1436
 
                    hook(result)
1437
 
        finally:
1438
 
            source.unlock()
1439
 
        return result
1440
 
 
1441
 
    def _get_parent_location(self):
1442
 
        _locs = ['parent', 'pull', 'x-pull']
1443
 
        for l in _locs:
1444
 
            try:
1445
 
                return self.control_files.get(l).read().strip('\n')
1446
 
            except NoSuchFile:
1447
 
                pass
1448
 
        return None
1449
 
 
1450
 
    @needs_read_lock
1451
 
    def push(self, target, overwrite=False, stop_revision=None,
1452
 
        _hook_master=None, _run_hooks=True):
1453
 
        """See Branch.push.
1454
 
        
1455
 
        :param _hook_master: Private parameter - set the branch to 
1456
 
            be supplied as the master to push hooks.
1457
 
        :param _run_hooks: Private parameter - allow disabling of
1458
 
            hooks, used when pushing to a master branch.
1459
 
        """
1460
 
        result = PushResult()
1461
 
        result.source_branch = self
1462
 
        result.target_branch = target
1463
 
        target.lock_write()
1464
 
        try:
1465
 
            result.old_revno, result.old_revid = target.last_revision_info()
1466
 
            try:
1467
 
                target.update_revisions(self, stop_revision)
1468
 
            except DivergedBranches:
1469
 
                if not overwrite:
1470
 
                    raise
1471
 
            if overwrite:
1472
 
                target.set_revision_history(self.revision_history())
1473
 
            result.tag_conflicts = self.tags.merge_to(target.tags)
1474
 
            result.new_revno, result.new_revid = target.last_revision_info()
1475
 
            if _hook_master:
1476
 
                result.master_branch = _hook_master
1477
 
                result.local_branch = target
1478
 
            else:
1479
 
                result.master_branch = target
1480
 
                result.local_branch = None
1481
 
            if _run_hooks:
1482
 
                for hook in Branch.hooks['post_push']:
1483
 
                    hook(result)
1484
 
        finally:
1485
 
            target.unlock()
1486
 
        return result
1487
 
 
1488
 
    def get_parent(self):
1489
 
        """See Branch.get_parent."""
1490
 
 
1491
 
        assert self.base[-1] == '/'
1492
 
        parent = self._get_parent_location()
1493
 
        if parent is None:
1494
 
            return parent
1495
 
        # This is an old-format absolute path to a local branch
1496
 
        # turn it into a url
1497
 
        if parent.startswith('/'):
1498
 
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
1499
 
        try:
1500
 
            return urlutils.join(self.base[:-1], parent)
1501
 
        except errors.InvalidURLJoin, e:
1502
 
            raise errors.InaccessibleParent(parent, self.base)
1503
 
 
1504
 
    def get_push_location(self):
1505
 
        """See Branch.get_push_location."""
1506
 
        push_loc = self.get_config().get_user_option('push_location')
1507
 
        return push_loc
1508
 
 
1509
 
    def set_push_location(self, location):
1510
 
        """See Branch.set_push_location."""
1511
 
        self.get_config().set_user_option(
1512
 
            'push_location', location,
1513
 
            store=_mod_config.STORE_LOCATION_NORECURSE)
1514
 
 
1515
 
    @needs_write_lock
1516
 
    def set_parent(self, url):
1517
 
        """See Branch.set_parent."""
1518
 
        # TODO: Maybe delete old location files?
1519
 
        # URLs should never be unicode, even on the local fs,
1520
 
        # FIXUP this and get_parent in a future branch format bump:
1521
 
        # read and rewrite the file, and have the new format code read
1522
 
        # using .get not .get_utf8. RBC 20060125
1523
 
        if url is not None:
1524
 
            if isinstance(url, unicode):
1525
 
                try: 
1526
 
                    url = url.encode('ascii')
1527
 
                except UnicodeEncodeError:
1528
 
                    raise bzrlib.errors.InvalidURL(url,
1529
 
                        "Urls must be 7-bit ascii, "
1530
 
                        "use bzrlib.urlutils.escape")
1531
 
            url = urlutils.relative_url(self.base, url)
1532
 
        self._set_parent_location(url)
1533
 
 
1534
 
    def _set_parent_location(self, url):
1535
 
        if url is None:
1536
 
            self.control_files._transport.delete('parent')
1537
 
        else:
1538
 
            assert isinstance(url, str)
1539
 
            self.control_files.put_bytes('parent', url + '\n')
1540
 
 
1541
 
    @deprecated_function(zero_nine)
1542
 
    def tree_config(self):
1543
 
        """DEPRECATED; call get_config instead.  
1544
 
        TreeConfig has become part of BranchConfig."""
1545
 
        return TreeConfig(self)
1546
 
 
1547
 
 
1548
 
class BzrBranch5(BzrBranch):
1549
 
    """A format 5 branch. This supports new features over plan branches.
1550
 
 
1551
 
    It has support for a master_branch which is the data for bound branches.
1552
 
    """
1553
 
 
1554
 
    def __init__(self,
1555
 
                 _format,
1556
 
                 _control_files,
1557
 
                 a_bzrdir,
1558
 
                 _repository):
1559
 
        super(BzrBranch5, self).__init__(_format=_format,
1560
 
                                         _control_files=_control_files,
1561
 
                                         a_bzrdir=a_bzrdir,
1562
 
                                         _repository=_repository)
1563
 
        
1564
 
    @needs_write_lock
1565
 
    def pull(self, source, overwrite=False, stop_revision=None,
1566
 
        _run_hooks=True):
1567
 
        """Extends branch.pull to be bound branch aware.
1568
 
        
1569
 
        :param _run_hooks: Private parameter used to force hook running
1570
 
            off during bound branch double-pushing.
1571
 
        """
1572
 
        bound_location = self.get_bound_location()
1573
 
        master_branch = None
1574
 
        if bound_location and source.base != bound_location:
1575
 
            # not pulling from master, so we need to update master.
1576
 
            master_branch = self.get_master_branch()
1577
 
            master_branch.lock_write()
1578
 
        try:
1579
 
            if master_branch:
1580
 
                # pull from source into master.
1581
 
                master_branch.pull(source, overwrite, stop_revision,
1582
 
                    _run_hooks=False)
1583
 
            return super(BzrBranch5, self).pull(source, overwrite,
1584
 
                stop_revision, _hook_master=master_branch,
1585
 
                _run_hooks=_run_hooks)
1586
 
        finally:
1587
 
            if master_branch:
1588
 
                master_branch.unlock()
1589
 
 
1590
 
    @needs_read_lock
1591
 
    def push(self, target, overwrite=False, stop_revision=None):
1592
 
        """Updates branch.push to be bound branch aware."""
1593
 
        bound_location = target.get_bound_location()
1594
 
        master_branch = None
1595
 
        if bound_location and target.base != bound_location:
1596
 
            # not pushing to master, so we need to update master.
1597
 
            master_branch = target.get_master_branch()
1598
 
            master_branch.lock_write()
1599
 
        try:
1600
 
            if master_branch:
1601
 
                # push into the master from this branch.
1602
 
                super(BzrBranch5, self).push(master_branch, overwrite,
1603
 
                    stop_revision, _run_hooks=False)
1604
 
            # and push into the target branch from this. Note that we push from
1605
 
            # this branch again, because its considered the highest bandwidth
1606
 
            # repository.
1607
 
            return super(BzrBranch5, self).push(target, overwrite,
1608
 
                stop_revision, _hook_master=master_branch)
1609
 
        finally:
1610
 
            if master_branch:
1611
 
                master_branch.unlock()
1612
 
 
1613
 
    def get_bound_location(self):
1614
 
        try:
1615
 
            return self.control_files.get_utf8('bound').read()[:-1]
1616
 
        except errors.NoSuchFile:
1617
 
            return None
1618
 
 
1619
 
    @needs_read_lock
1620
 
    def get_master_branch(self):
1621
 
        """Return the branch we are bound to.
1622
 
        
1623
 
        :return: Either a Branch, or None
1624
 
 
1625
 
        This could memoise the branch, but if thats done
1626
 
        it must be revalidated on each new lock.
1627
 
        So for now we just don't memoise it.
1628
 
        # RBC 20060304 review this decision.
1629
 
        """
1630
 
        bound_loc = self.get_bound_location()
1631
 
        if not bound_loc:
1632
 
            return None
1633
 
        try:
1634
 
            return Branch.open(bound_loc)
1635
 
        except (errors.NotBranchError, errors.ConnectionError), e:
1636
 
            raise errors.BoundBranchConnectionFailure(
1637
 
                    self, bound_loc, e)
1638
 
 
1639
 
    @needs_write_lock
1640
 
    def set_bound_location(self, location):
1641
 
        """Set the target where this branch is bound to.
1642
 
 
1643
 
        :param location: URL to the target branch
1644
 
        """
1645
 
        if location:
1646
 
            self.control_files.put_utf8('bound', location+'\n')
1647
 
        else:
1648
 
            try:
1649
 
                self.control_files._transport.delete('bound')
1650
 
            except NoSuchFile:
1651
 
                return False
 
1313
        
 
1314
        
 
1315
        
 
1316
 
 
1317
 
 
1318
class ScratchBranch(_Branch):
 
1319
    """Special test class: a branch that cleans up after itself.
 
1320
 
 
1321
    >>> b = ScratchBranch()
 
1322
    >>> isdir(b.base)
 
1323
    True
 
1324
    >>> bd = b.base
 
1325
    >>> b.destroy()
 
1326
    >>> isdir(bd)
 
1327
    False
 
1328
    """
 
1329
    def __init__(self, files=[], dirs=[], base=None):
 
1330
        """Make a test branch.
 
1331
 
 
1332
        This creates a temporary directory and runs init-tree in it.
 
1333
 
 
1334
        If any files are listed, they are created in the working copy.
 
1335
        """
 
1336
        from tempfile import mkdtemp
 
1337
        init = False
 
1338
        if base is None:
 
1339
            base = mkdtemp()
 
1340
            init = True
 
1341
        if isinstance(base, basestring):
 
1342
            base = get_transport(base)
 
1343
        _Branch.__init__(self, base, init=init)
 
1344
        for d in dirs:
 
1345
            self._transport.mkdir(d)
 
1346
            
 
1347
        for f in files:
 
1348
            self._transport.put(f, 'content of %s' % f)
 
1349
 
 
1350
 
 
1351
    def clone(self):
 
1352
        """
 
1353
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
1354
        >>> clone = orig.clone()
 
1355
        >>> if os.name != 'nt':
 
1356
        ...   os.path.samefile(orig.base, clone.base)
 
1357
        ... else:
 
1358
        ...   orig.base == clone.base
 
1359
        ...
 
1360
        False
 
1361
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
 
1362
        True
 
1363
        """
 
1364
        from shutil import copytree
 
1365
        from tempfile import mkdtemp
 
1366
        base = mkdtemp()
 
1367
        os.rmdir(base)
 
1368
        copytree(self.base, base, symlinks=True)
 
1369
        return ScratchBranch(base=base)
 
1370
 
 
1371
    def __del__(self):
 
1372
        self.destroy()
 
1373
 
 
1374
    def destroy(self):
 
1375
        """Destroy the test branch, removing the scratch directory."""
 
1376
        from shutil import rmtree
 
1377
        try:
 
1378
            if self.base:
 
1379
                mutter("delete ScratchBranch %s" % self.base)
 
1380
                rmtree(self.base)
 
1381
        except OSError, e:
 
1382
            # Work around for shutil.rmtree failing on Windows when
 
1383
            # readonly files are encountered
 
1384
            mutter("hit exception in destroying ScratchBranch: %s" % e)
 
1385
            for root, dirs, files in os.walk(self.base, topdown=False):
 
1386
                for name in files:
 
1387
                    os.chmod(os.path.join(root, name), 0700)
 
1388
            rmtree(self.base)
 
1389
        self._transport = None
 
1390
 
 
1391
    
 
1392
 
 
1393
######################################################################
 
1394
# predicates
 
1395
 
 
1396
 
 
1397
def is_control_file(filename):
 
1398
    ## FIXME: better check
 
1399
    filename = os.path.normpath(filename)
 
1400
    while filename != '':
 
1401
        head, tail = os.path.split(filename)
 
1402
        ## mutter('check %r for control file' % ((head, tail), ))
 
1403
        if tail == bzrlib.BZRDIR:
1652
1404
            return True
1653
 
 
1654
 
    @needs_write_lock
1655
 
    def bind(self, other):
1656
 
        """Bind this branch to the branch other.
1657
 
 
1658
 
        This does not push or pull data between the branches, though it does
1659
 
        check for divergence to raise an error when the branches are not
1660
 
        either the same, or one a prefix of the other. That behaviour may not
1661
 
        be useful, so that check may be removed in future.
1662
 
        
1663
 
        :param other: The branch to bind to
1664
 
        :type other: Branch
1665
 
        """
1666
 
        # TODO: jam 20051230 Consider checking if the target is bound
1667
 
        #       It is debatable whether you should be able to bind to
1668
 
        #       a branch which is itself bound.
1669
 
        #       Committing is obviously forbidden,
1670
 
        #       but binding itself may not be.
1671
 
        #       Since we *have* to check at commit time, we don't
1672
 
        #       *need* to check here
1673
 
 
1674
 
        # we want to raise diverged if:
1675
 
        # last_rev is not in the other_last_rev history, AND
1676
 
        # other_last_rev is not in our history, and do it without pulling
1677
 
        # history around
1678
 
        last_rev = self.last_revision()
1679
 
        if last_rev is not None:
1680
 
            other.lock_read()
1681
 
            try:
1682
 
                other_last_rev = other.last_revision()
1683
 
                if other_last_rev is not None:
1684
 
                    # neither branch is new, we have to do some work to
1685
 
                    # ascertain diversion.
1686
 
                    remote_graph = other.repository.get_revision_graph(
1687
 
                        other_last_rev)
1688
 
                    local_graph = self.repository.get_revision_graph(last_rev)
1689
 
                    if (last_rev not in remote_graph and
1690
 
                        other_last_rev not in local_graph):
1691
 
                        raise errors.DivergedBranches(self, other)
1692
 
            finally:
1693
 
                other.unlock()
1694
 
        self.set_bound_location(other.base)
1695
 
 
1696
 
    @needs_write_lock
1697
 
    def unbind(self):
1698
 
        """If bound, unbind"""
1699
 
        return self.set_bound_location(None)
1700
 
 
1701
 
    @needs_write_lock
1702
 
    def update(self):
1703
 
        """Synchronise this branch with the master branch if any. 
1704
 
 
1705
 
        :return: None or the last_revision that was pivoted out during the
1706
 
                 update.
1707
 
        """
1708
 
        master = self.get_master_branch()
1709
 
        if master is not None:
1710
 
            old_tip = self.last_revision()
1711
 
            self.pull(master, overwrite=True)
1712
 
            if old_tip in self.repository.get_ancestry(self.last_revision()):
1713
 
                return None
1714
 
            return old_tip
1715
 
        return None
1716
 
 
1717
 
 
1718
 
class BzrBranchExperimental(BzrBranch5):
1719
 
    """Bzr experimental branch format
1720
 
 
1721
 
    This format has:
1722
 
     - a revision-history file.
1723
 
     - a format string
1724
 
     - a lock dir guarding the branch itself
1725
 
     - all of this stored in a branch/ subdirectory
1726
 
     - works with shared repositories.
1727
 
     - a tag dictionary in the branch
1728
 
 
1729
 
    This format is new in bzr 0.15, but shouldn't be used for real data, 
1730
 
    only for testing.
1731
 
 
1732
 
    This class acts as it's own BranchFormat.
1733
 
    """
1734
 
 
1735
 
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1736
 
 
1737
 
    @classmethod
1738
 
    def get_format_string(cls):
1739
 
        """See BranchFormat.get_format_string()."""
1740
 
        return "Bazaar-NG branch format experimental\n"
1741
 
 
1742
 
    @classmethod
1743
 
    def get_format_description(cls):
1744
 
        """See BranchFormat.get_format_description()."""
1745
 
        return "Experimental branch format"
1746
 
 
1747
 
    @classmethod
1748
 
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1749
 
            lock_class):
1750
 
        branch_transport = a_bzrdir.get_branch_transport(cls)
1751
 
        control_files = lockable_files.LockableFiles(branch_transport,
1752
 
            lock_filename, lock_class)
1753
 
        control_files.create_lock()
1754
 
        control_files.lock_write()
1755
 
        try:
1756
 
            for filename, content in utf8_files:
1757
 
                control_files.put_utf8(filename, content)
1758
 
        finally:
1759
 
            control_files.unlock()
1760
 
        
1761
 
    @classmethod
1762
 
    def initialize(cls, a_bzrdir):
1763
 
        """Create a branch of this format in a_bzrdir."""
1764
 
        utf8_files = [('format', cls.get_format_string()),
1765
 
                      ('revision-history', ''),
1766
 
                      ('branch-name', ''),
1767
 
                      ('tags', ''),
1768
 
                      ]
1769
 
        cls._initialize_control_files(a_bzrdir, utf8_files,
1770
 
            'lock', lockdir.LockDir)
1771
 
        return cls.open(a_bzrdir, _found=True)
1772
 
 
1773
 
    @classmethod
1774
 
    def open(cls, a_bzrdir, _found=False):
1775
 
        """Return the branch object for a_bzrdir
1776
 
 
1777
 
        _found is a private parameter, do not use it. It is used to indicate
1778
 
               if format probing has already be done.
1779
 
        """
1780
 
        if not _found:
1781
 
            format = BranchFormat.find_format(a_bzrdir)
1782
 
            assert format.__class__ == cls
1783
 
        transport = a_bzrdir.get_branch_transport(None)
1784
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1785
 
                                                     lockdir.LockDir)
1786
 
        return cls(_format=cls,
1787
 
            _control_files=control_files,
1788
 
            a_bzrdir=a_bzrdir,
1789
 
            _repository=a_bzrdir.find_repository())
1790
 
 
1791
 
    @classmethod
1792
 
    def is_supported(cls):
1793
 
        return True
1794
 
 
1795
 
    def _make_tags(self):
1796
 
        return BasicTags(self)
1797
 
 
1798
 
    @classmethod
1799
 
    def supports_tags(cls):
1800
 
        return True
1801
 
 
1802
 
 
1803
 
BranchFormat.register_format(BzrBranchExperimental)
1804
 
 
1805
 
 
1806
 
class BzrBranch6(BzrBranch5):
1807
 
 
1808
 
    @needs_read_lock
1809
 
    def last_revision_info(self):
1810
 
        revision_string = self.control_files.get('last-revision').read()
1811
 
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1812
 
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1813
 
        revno = int(revno)
1814
 
        return revno, revision_id
1815
 
 
1816
 
    def last_revision(self):
1817
 
        """Return last revision id, or None"""
1818
 
        revision_id = self.last_revision_info()[1]
1819
 
        if revision_id == _mod_revision.NULL_REVISION:
1820
 
            revision_id = None
1821
 
        return revision_id
1822
 
 
1823
 
    def _write_last_revision_info(self, revno, revision_id):
1824
 
        """Simply write out the revision id, with no checks.
1825
 
 
1826
 
        Use set_last_revision_info to perform this safely.
1827
 
 
1828
 
        Does not update the revision_history cache.
1829
 
        Intended to be called by set_last_revision_info and
1830
 
        _write_revision_history.
1831
 
        """
1832
 
        if revision_id is None:
1833
 
            revision_id = 'null:'
1834
 
        out_string = '%d %s\n' % (revno, revision_id)
1835
 
        self.control_files.put_bytes('last-revision', out_string)
1836
 
 
1837
 
    @needs_write_lock
1838
 
    def set_last_revision_info(self, revno, revision_id):
1839
 
        revision_id = osutils.safe_revision_id(revision_id)
1840
 
        if self._get_append_revisions_only():
1841
 
            self._check_history_violation(revision_id)
1842
 
        self._write_last_revision_info(revno, revision_id)
1843
 
        self._clear_cached_state()
1844
 
 
1845
 
    def _check_history_violation(self, revision_id):
1846
 
        last_revision = self.last_revision()
1847
 
        if last_revision is None:
1848
 
            return
1849
 
        if last_revision not in self._lefthand_history(revision_id):
1850
 
            raise errors.AppendRevisionsOnlyViolation(self.base)
1851
 
 
1852
 
    def _gen_revision_history(self):
1853
 
        """Generate the revision history from last revision
1854
 
        """
1855
 
        history = list(self.repository.iter_reverse_revision_history(
1856
 
            self.last_revision()))
1857
 
        history.reverse()
1858
 
        return history
1859
 
 
1860
 
    def _write_revision_history(self, history):
1861
 
        """Factored out of set_revision_history.
1862
 
 
1863
 
        This performs the actual writing to disk, with format-specific checks.
1864
 
        It is intended to be called by BzrBranch5.set_revision_history.
1865
 
        """
1866
 
        if len(history) == 0:
1867
 
            last_revision = 'null:'
1868
 
        else:
1869
 
            if history != self._lefthand_history(history[-1]):
1870
 
                raise errors.NotLefthandHistory(history)
1871
 
            last_revision = history[-1]
1872
 
        if self._get_append_revisions_only():
1873
 
            self._check_history_violation(last_revision)
1874
 
        self._write_last_revision_info(len(history), last_revision)
1875
 
 
1876
 
    @needs_write_lock
1877
 
    def append_revision(self, *revision_ids):
1878
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1879
 
        if len(revision_ids) == 0:
1880
 
            return
1881
 
        prev_revno, prev_revision = self.last_revision_info()
1882
 
        for revision in self.repository.get_revisions(revision_ids):
1883
 
            if prev_revision == _mod_revision.NULL_REVISION:
1884
 
                if revision.parent_ids != []:
1885
 
                    raise errors.NotLeftParentDescendant(self, prev_revision,
1886
 
                                                         revision.revision_id)
1887
 
            else:
1888
 
                if revision.parent_ids[0] != prev_revision:
1889
 
                    raise errors.NotLeftParentDescendant(self, prev_revision,
1890
 
                                                         revision.revision_id)
1891
 
            prev_revision = revision.revision_id
1892
 
        self.set_last_revision_info(prev_revno + len(revision_ids),
1893
 
                                    revision_ids[-1])
1894
 
 
1895
 
    @needs_write_lock
1896
 
    def _set_parent_location(self, url):
1897
 
        """Set the parent branch"""
1898
 
        self._set_config_location('parent_location', url, make_relative=True)
1899
 
 
1900
 
    @needs_read_lock
1901
 
    def _get_parent_location(self):
1902
 
        """Set the parent branch"""
1903
 
        return self._get_config_location('parent_location')
1904
 
 
1905
 
    def set_push_location(self, location):
1906
 
        """See Branch.set_push_location."""
1907
 
        self._set_config_location('push_location', location)
1908
 
 
1909
 
    def set_bound_location(self, location):
1910
 
        """See Branch.set_push_location."""
1911
 
        result = None
1912
 
        config = self.get_config()
1913
 
        if location is None:
1914
 
            if config.get_user_option('bound') != 'True':
1915
 
                return False
1916
 
            else:
1917
 
                config.set_user_option('bound', 'False')
1918
 
                return True
1919
 
        else:
1920
 
            self._set_config_location('bound_location', location,
1921
 
                                      config=config)
1922
 
            config.set_user_option('bound', 'True')
1923
 
        return True
1924
 
 
1925
 
    def _get_bound_location(self, bound):
1926
 
        """Return the bound location in the config file.
1927
 
 
1928
 
        Return None if the bound parameter does not match"""
1929
 
        config = self.get_config()
1930
 
        config_bound = (config.get_user_option('bound') == 'True')
1931
 
        if config_bound != bound:
1932
 
            return None
1933
 
        return self._get_config_location('bound_location', config=config)
1934
 
 
1935
 
    def get_bound_location(self):
1936
 
        """See Branch.set_push_location."""
1937
 
        return self._get_bound_location(True)
1938
 
 
1939
 
    def get_old_bound_location(self):
1940
 
        """See Branch.get_old_bound_location"""
1941
 
        return self._get_bound_location(False)
1942
 
 
1943
 
    def set_append_revisions_only(self, enabled):
1944
 
        if enabled:
1945
 
            value = 'True'
1946
 
        else:
1947
 
            value = 'False'
1948
 
        self.get_config().set_user_option('append_revisions_only', value)
1949
 
 
1950
 
    def _get_append_revisions_only(self):
1951
 
        value = self.get_config().get_user_option('append_revisions_only')
1952
 
        return value == 'True'
1953
 
 
1954
 
    def _synchronize_history(self, destination, revision_id):
1955
 
        """Synchronize last revision and revision history between branches.
1956
 
 
1957
 
        This version is most efficient when the destination is also a
1958
 
        BzrBranch6, but works for BzrBranch5, as long as the destination's
1959
 
        repository contains all the lefthand ancestors of the intended
1960
 
        last_revision.  If not, set_last_revision_info will fail.
1961
 
 
1962
 
        :param destination: The branch to copy the history into
1963
 
        :param revision_id: The revision-id to truncate history at.  May
1964
 
          be None to copy complete history.
1965
 
        """
1966
 
        if revision_id is None:
1967
 
            revno, revision_id = self.last_revision_info()
1968
 
        else:
1969
 
            revno = self.revision_id_to_revno(revision_id)
1970
 
        destination.set_last_revision_info(revno, revision_id)
1971
 
 
1972
 
    def _make_tags(self):
1973
 
        return BasicTags(self)
1974
 
 
1975
 
 
1976
 
class BranchTestProviderAdapter(object):
1977
 
    """A tool to generate a suite testing multiple branch formats at once.
1978
 
 
1979
 
    This is done by copying the test once for each transport and injecting
1980
 
    the transport_server, transport_readonly_server, and branch_format
1981
 
    classes into each copy. Each copy is also given a new id() to make it
1982
 
    easy to identify.
1983
 
    """
1984
 
 
1985
 
    def __init__(self, transport_server, transport_readonly_server, formats):
1986
 
        self._transport_server = transport_server
1987
 
        self._transport_readonly_server = transport_readonly_server
1988
 
        self._formats = formats
1989
 
    
1990
 
    def adapt(self, test):
1991
 
        result = TestSuite()
1992
 
        for branch_format, bzrdir_format in self._formats:
1993
 
            new_test = deepcopy(test)
1994
 
            new_test.transport_server = self._transport_server
1995
 
            new_test.transport_readonly_server = self._transport_readonly_server
1996
 
            new_test.bzrdir_format = bzrdir_format
1997
 
            new_test.branch_format = branch_format
1998
 
            def make_new_test_id():
1999
 
                # the format can be either a class or an instance
2000
 
                name = getattr(branch_format, '__name__',
2001
 
                        branch_format.__class__.__name__)
2002
 
                new_id = "%s(%s)" % (new_test.id(), name)
2003
 
                return lambda: new_id
2004
 
            new_test.id = make_new_test_id()
2005
 
            result.addTest(new_test)
2006
 
        return result
2007
 
 
2008
 
 
2009
 
######################################################################
2010
 
# results of operations
2011
 
 
2012
 
 
2013
 
class _Result(object):
2014
 
 
2015
 
    def _show_tag_conficts(self, to_file):
2016
 
        if not getattr(self, 'tag_conflicts', None):
2017
 
            return
2018
 
        to_file.write('Conflicting tags:\n')
2019
 
        for name, value1, value2 in self.tag_conflicts:
2020
 
            to_file.write('    %s\n' % (name, ))
2021
 
 
2022
 
 
2023
 
class PullResult(_Result):
2024
 
    """Result of a Branch.pull operation.
2025
 
 
2026
 
    :ivar old_revno: Revision number before pull.
2027
 
    :ivar new_revno: Revision number after pull.
2028
 
    :ivar old_revid: Tip revision id before pull.
2029
 
    :ivar new_revid: Tip revision id after pull.
2030
 
    :ivar source_branch: Source (local) branch object.
2031
 
    :ivar master_branch: Master branch of the target, or None.
2032
 
    :ivar target_branch: Target/destination branch object.
2033
 
    """
2034
 
 
2035
 
    def __int__(self):
2036
 
        # DEPRECATED: pull used to return the change in revno
2037
 
        return self.new_revno - self.old_revno
2038
 
 
2039
 
    def report(self, to_file):
2040
 
        if self.old_revid == self.new_revid:
2041
 
            to_file.write('No revisions to pull.\n')
2042
 
        else:
2043
 
            to_file.write('Now on revision %d.\n' % self.new_revno)
2044
 
        self._show_tag_conficts(to_file)
2045
 
 
2046
 
 
2047
 
class PushResult(_Result):
2048
 
    """Result of a Branch.push operation.
2049
 
 
2050
 
    :ivar old_revno: Revision number before push.
2051
 
    :ivar new_revno: Revision number after push.
2052
 
    :ivar old_revid: Tip revision id before push.
2053
 
    :ivar new_revid: Tip revision id after push.
2054
 
    :ivar source_branch: Source branch object.
2055
 
    :ivar master_branch: Master branch of the target, or None.
2056
 
    :ivar target_branch: Target/destination branch object.
2057
 
    """
2058
 
 
2059
 
    def __int__(self):
2060
 
        # DEPRECATED: push used to return the change in revno
2061
 
        return self.new_revno - self.old_revno
2062
 
 
2063
 
    def report(self, to_file):
2064
 
        """Write a human-readable description of the result."""
2065
 
        if self.old_revid == self.new_revid:
2066
 
            to_file.write('No new revisions to push.\n')
2067
 
        else:
2068
 
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2069
 
        self._show_tag_conficts(to_file)
2070
 
 
2071
 
 
2072
 
class BranchCheckResult(object):
2073
 
    """Results of checking branch consistency.
2074
 
 
2075
 
    :see: Branch.check
2076
 
    """
2077
 
 
2078
 
    def __init__(self, branch):
2079
 
        self.branch = branch
2080
 
 
2081
 
    def report_results(self, verbose):
2082
 
        """Report the check results via trace.note.
2083
 
        
2084
 
        :param verbose: Requests more detailed display of what was checked,
2085
 
            if any.
2086
 
        """
2087
 
        note('checked branch %s format %s',
2088
 
             self.branch.base,
2089
 
             self.branch._format)
2090
 
 
2091
 
 
2092
 
class Converter5to6(object):
2093
 
    """Perform an in-place upgrade of format 5 to format 6"""
2094
 
 
2095
 
    def convert(self, branch):
2096
 
        # Data for 5 and 6 can peacefully coexist.
2097
 
        format = BzrBranchFormat6()
2098
 
        new_branch = format.open(branch.bzrdir, _found=True)
2099
 
 
2100
 
        # Copy source data into target
2101
 
        new_branch.set_last_revision_info(*branch.last_revision_info())
2102
 
        new_branch.set_parent(branch.get_parent())
2103
 
        new_branch.set_bound_location(branch.get_bound_location())
2104
 
        new_branch.set_push_location(branch.get_push_location())
2105
 
 
2106
 
        # New branch has no tags by default
2107
 
        new_branch.tags._set_tag_dict({})
2108
 
 
2109
 
        # Copying done; now update target format
2110
 
        new_branch.control_files.put_utf8('format',
2111
 
            format.get_format_string())
2112
 
 
2113
 
        # Clean up old files
2114
 
        new_branch.control_files._transport.delete('revision-history')
2115
 
        try:
2116
 
            branch.set_parent(None)
2117
 
        except NoSuchFile:
2118
 
            pass
2119
 
        branch.set_bound_location(None)
 
1405
        if filename == head:
 
1406
            break
 
1407
        filename = head
 
1408
    return False
 
1409
 
 
1410
 
 
1411
 
 
1412
def gen_file_id(name):
 
1413
    """Return new file id.
 
1414
 
 
1415
    This should probably generate proper UUIDs, but for the moment we
 
1416
    cope with just randomness because running uuidgen every time is
 
1417
    slow."""
 
1418
    import re
 
1419
    from binascii import hexlify
 
1420
    from time import time
 
1421
 
 
1422
    # get last component
 
1423
    idx = name.rfind('/')
 
1424
    if idx != -1:
 
1425
        name = name[idx+1 : ]
 
1426
    idx = name.rfind('\\')
 
1427
    if idx != -1:
 
1428
        name = name[idx+1 : ]
 
1429
 
 
1430
    # make it not a hidden file
 
1431
    name = name.lstrip('.')
 
1432
 
 
1433
    # remove any wierd characters; we don't escape them but rather
 
1434
    # just pull them out
 
1435
    name = re.sub(r'[^\w.]', '', name)
 
1436
 
 
1437
    s = hexlify(rand_bytes(8))
 
1438
    return '-'.join((name, compact_date(time()), s))
 
1439
 
 
1440
 
 
1441
def gen_root_id():
 
1442
    """Return a new tree-root file id."""
 
1443
    return gen_file_id('TREE_ROOT')
 
1444
 
 
1445