~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-10 06:26:39 UTC
  • mto: This revision was merged to the branch mainline in revision 2070.
  • Revision ID: john@arbash-meinel.com-20061010062639-6d527d0f9a3401d8
Catch an exception while opening /dev/urandom rather than using os.path.exists()

Show diffs side-by-side

added added

removed removed

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