~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Heikki Paajanen's status -r patch

Show diffs side-by-side

added added

removed removed

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