/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2007-02-20 04:47:54 UTC
  • mto: (2220.2.19 tags)
  • mto: This revision was merged to the branch mainline in revision 2309.
  • Revision ID: mbp@sourcefrog.net-20070220044754-ptovhd212hs4vkkf
add bencode utility

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
from cStringIO import StringIO
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
 
21
lazy_import(globals(), """
 
22
from copy import deepcopy
 
23
from unittest import TestSuite
 
24
from warnings import warn
 
25
 
 
26
import bzrlib
 
27
from bzrlib import (
 
28
        bzrdir,
 
29
        cache_utf8,
 
30
        config as _mod_config,
 
31
        errors,
 
32
        lockdir,
 
33
        lockable_files,
 
34
        osutils,
 
35
        revision as _mod_revision,
 
36
        transport,
 
37
        tree,
 
38
        ui,
 
39
        urlutils,
 
40
        )
 
41
from bzrlib.config import BranchConfig, TreeConfig
 
42
from bzrlib.lockable_files import LockableFiles, TransportLock
 
43
""")
 
44
 
 
45
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
46
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
 
47
                           HistoryMissing, InvalidRevisionId,
 
48
                           InvalidRevisionNumber, LockError, NoSuchFile,
 
49
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
 
50
                           NotBranchError, UninitializableFormat,
 
51
                           UnlistableStore, UnlistableBranch,
 
52
                           )
 
53
from bzrlib.symbol_versioning import (deprecated_function,
 
54
                                      deprecated_method,
 
55
                                      DEPRECATED_PARAMETER,
 
56
                                      deprecated_passed,
 
57
                                      zero_eight, zero_nine,
 
58
                                      )
 
59
from bzrlib.trace import mutter, note
 
60
 
 
61
 
 
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
63
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
64
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
65
 
 
66
 
 
67
# TODO: Maybe include checks for common corruption of newlines, etc?
 
68
 
 
69
# TODO: Some operations like log might retrieve the same revisions
 
70
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
71
# cache in memory to make this faster.  In general anything can be
 
72
# cached in memory between lock and unlock operations. .. nb thats
 
73
# what the transaction identity map provides
 
74
 
 
75
 
 
76
######################################################################
 
77
# tag storage
 
78
 
 
79
 
 
80
class _TagStore(object):
 
81
    def __init__(self, repository):
 
82
        self.repository = repository
 
83
 
 
84
class _DisabledTagStore(_TagStore):
 
85
    """Tag storage that refuses to store anything.
 
86
 
 
87
    This is used by older formats that can't store tags.
 
88
    """
 
89
 
 
90
    def _not_supported(self, *a, **k):
 
91
        raise errors.TagsNotSupported(self.repository)
 
92
 
 
93
    def supports_tags(self):
 
94
        return False
 
95
 
 
96
    set_tag = _not_supported
 
97
    get_tag_dict = _not_supported
 
98
    _set_tag_dict = _not_supported
 
99
    lookup_tag = _not_supported
 
100
 
 
101
 
 
102
class _BasicTagStore(_TagStore):
 
103
    """Tag storage in an unversioned repository control file.
 
104
    """
 
105
 
 
106
    def supports_tags(self):
 
107
        return True
 
108
 
 
109
    def set_tag(self, tag_name, tag_target):
 
110
        """Add a tag definition to the repository.
 
111
 
 
112
        Behaviour if the tag is already present is not defined (yet).
 
113
        """
 
114
        # all done with a write lock held, so this looks atomic
 
115
        self.repository.lock_write()
 
116
        try:
 
117
            td = self.get_tag_dict()
 
118
            td[tag_name] = tag_target
 
119
            self._set_tag_dict(td)
 
120
        finally:
 
121
            self.repository.unlock()
 
122
 
 
123
    def lookup_tag(self, tag_name):
 
124
        """Return the referent string of a tag"""
 
125
        td = self.get_tag_dict()
 
126
        try:
 
127
            return td[tag_name]
 
128
        except KeyError:
 
129
            raise errors.NoSuchTag(tag_name)
 
130
 
 
131
    def get_tag_dict(self):
 
132
        self.repository.lock_read()
 
133
        try:
 
134
            tag_content = self.repository.control_files.get_utf8('tags').read()
 
135
            return self._deserialize_tag_dict(tag_content)
 
136
        finally:
 
137
            self.repository.unlock()
 
138
 
 
139
    def _set_tag_dict(self, new_dict):
 
140
        """Replace all tag definitions
 
141
 
 
142
        :param new_dict: Dictionary from tag name to target.
 
143
        """
 
144
        self.repository.lock_read()
 
145
        try:
 
146
            self.repository.control_files.put_utf8('tags',
 
147
                self._serialize_tag_dict(new_dict))
 
148
        finally:
 
149
            self.repository.unlock()
 
150
 
 
151
    def _serialize_tag_dict(self, tag_dict):
 
152
        s = []
 
153
        for tag, target in sorted(tag_dict.items()):
 
154
            # TODO: check that tag names and targets are acceptable
 
155
            s.append(tag + '\t' + target + '\n')
 
156
        return ''.join(s)
 
157
 
 
158
    def _deserialize_tag_dict(self, tag_content):
 
159
        """Convert the tag file into a dictionary of tags"""
 
160
        d = {}
 
161
        for l in tag_content.splitlines():
 
162
            tag, target = l.split('\t', 1)
 
163
            d[tag] = target
 
164
        return d
 
165
 
 
166
 
 
167
######################################################################
 
168
# branch objects
 
169
 
 
170
class Branch(object):
 
171
    """Branch holding a history of revisions.
 
172
 
 
173
    base
 
174
        Base directory/url of the branch.
 
175
    """
 
176
    # this is really an instance variable - FIXME move it there
 
177
    # - RBC 20060112
 
178
    base = None
 
179
 
 
180
    # override this to set the strategy for storing tags
 
181
    _tag_store_class = _DisabledTagStore
 
182
 
 
183
    def __init__(self, *ignored, **ignored_too):
 
184
        self._tag_store = self._tag_store_class(self)
 
185
 
 
186
    def break_lock(self):
 
187
        """Break a lock if one is present from another instance.
 
188
 
 
189
        Uses the ui factory to ask for confirmation if the lock may be from
 
190
        an active process.
 
191
 
 
192
        This will probe the repository for its lock as well.
 
193
        """
 
194
        self.control_files.break_lock()
 
195
        self.repository.break_lock()
 
196
        master = self.get_master_branch()
 
197
        if master is not None:
 
198
            master.break_lock()
 
199
 
 
200
    @staticmethod
 
201
    @deprecated_method(zero_eight)
 
202
    def open_downlevel(base):
 
203
        """Open a branch which may be of an old format."""
 
204
        return Branch.open(base, _unsupported=True)
 
205
        
 
206
    @staticmethod
 
207
    def open(base, _unsupported=False):
 
208
        """Open the branch rooted at base.
 
209
 
 
210
        For instance, if the branch is at URL/.bzr/branch,
 
211
        Branch.open(URL) -> a Branch instance.
 
212
        """
 
213
        control = bzrdir.BzrDir.open(base, _unsupported)
 
214
        return control.open_branch(_unsupported)
 
215
 
 
216
    @staticmethod
 
217
    def open_containing(url):
 
218
        """Open an existing branch which contains url.
 
219
        
 
220
        This probes for a branch at url, and searches upwards from there.
 
221
 
 
222
        Basically we keep looking up until we find the control directory or
 
223
        run into the root.  If there isn't one, raises NotBranchError.
 
224
        If there is one and it is either an unrecognised format or an unsupported 
 
225
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
226
        If there is one, it is returned, along with the unused portion of url.
 
227
        """
 
228
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
229
        return control.open_branch(), relpath
 
230
 
 
231
    @staticmethod
 
232
    @deprecated_function(zero_eight)
 
233
    def initialize(base):
 
234
        """Create a new working tree and branch, rooted at 'base' (url)
 
235
 
 
236
        NOTE: This will soon be deprecated in favour of creation
 
237
        through a BzrDir.
 
238
        """
 
239
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
 
240
 
 
241
    @deprecated_function(zero_eight)
 
242
    def setup_caching(self, cache_root):
 
243
        """Subclasses that care about caching should override this, and set
 
244
        up cached stores located under cache_root.
 
245
        
 
246
        NOTE: This is unused.
 
247
        """
 
248
        pass
 
249
 
 
250
    def get_config(self):
 
251
        return BranchConfig(self)
 
252
 
 
253
    def _get_nick(self):
 
254
        return self.get_config().get_nickname()
 
255
 
 
256
    def _set_nick(self, nick):
 
257
        self.get_config().set_user_option('nickname', nick)
 
258
 
 
259
    nick = property(_get_nick, _set_nick)
 
260
 
 
261
    def is_locked(self):
 
262
        raise NotImplementedError(self.is_locked)
 
263
 
 
264
    def lock_write(self):
 
265
        raise NotImplementedError(self.lock_write)
 
266
 
 
267
    def lock_read(self):
 
268
        raise NotImplementedError(self.lock_read)
 
269
 
 
270
    def unlock(self):
 
271
        raise NotImplementedError(self.unlock)
 
272
 
 
273
    def peek_lock_mode(self):
 
274
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
275
        raise NotImplementedError(self.peek_lock_mode)
 
276
 
 
277
    def get_physical_lock_status(self):
 
278
        raise NotImplementedError(self.get_physical_lock_status)
 
279
 
 
280
    def abspath(self, name):
 
281
        """Return absolute filename for something in the branch
 
282
        
 
283
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
284
        method and not a tree method.
 
285
        """
 
286
        raise NotImplementedError(self.abspath)
 
287
 
 
288
    def bind(self, other):
 
289
        """Bind the local branch the other branch.
 
290
 
 
291
        :param other: The branch to bind to
 
292
        :type other: Branch
 
293
        """
 
294
        raise errors.UpgradeRequired(self.base)
 
295
 
 
296
    @needs_write_lock
 
297
    def fetch(self, from_branch, last_revision=None, pb=None):
 
298
        """Copy revisions from from_branch into this branch.
 
299
 
 
300
        :param from_branch: Where to copy from.
 
301
        :param last_revision: What revision to stop at (None for at the end
 
302
                              of the branch.
 
303
        :param pb: An optional progress bar to use.
 
304
 
 
305
        Returns the copied revision count and the failed revisions in a tuple:
 
306
        (copied, failures).
 
307
        """
 
308
        if self.base == from_branch.base:
 
309
            return (0, [])
 
310
        if pb is None:
 
311
            nested_pb = ui.ui_factory.nested_progress_bar()
 
312
            pb = nested_pb
 
313
        else:
 
314
            nested_pb = None
 
315
 
 
316
        from_branch.lock_read()
 
317
        try:
 
318
            if last_revision is None:
 
319
                pb.update('get source history')
 
320
                from_history = from_branch.revision_history()
 
321
                if from_history:
 
322
                    last_revision = from_history[-1]
 
323
                else:
 
324
                    # no history in the source branch
 
325
                    last_revision = _mod_revision.NULL_REVISION
 
326
            return self.repository.fetch(from_branch.repository,
 
327
                                         revision_id=last_revision,
 
328
                                         pb=nested_pb)
 
329
        finally:
 
330
            if nested_pb is not None:
 
331
                nested_pb.finished()
 
332
            from_branch.unlock()
 
333
 
 
334
    def get_bound_location(self):
 
335
        """Return the URL of the branch we are bound to.
 
336
 
 
337
        Older format branches cannot bind, please be sure to use a metadir
 
338
        branch.
 
339
        """
 
340
        return None
 
341
    
 
342
    def get_commit_builder(self, parents, config=None, timestamp=None, 
 
343
                           timezone=None, committer=None, revprops=None, 
 
344
                           revision_id=None):
 
345
        """Obtain a CommitBuilder for this branch.
 
346
        
 
347
        :param parents: Revision ids of the parents of the new revision.
 
348
        :param config: Optional configuration to use.
 
349
        :param timestamp: Optional timestamp recorded for commit.
 
350
        :param timezone: Optional timezone for timestamp.
 
351
        :param committer: Optional committer to set for commit.
 
352
        :param revprops: Optional dictionary of revision properties.
 
353
        :param revision_id: Optional revision id.
 
354
        """
 
355
 
 
356
        if config is None:
 
357
            config = self.get_config()
 
358
        
 
359
        return self.repository.get_commit_builder(self, parents, config, 
 
360
            timestamp, timezone, committer, revprops, revision_id)
 
361
 
 
362
    def get_master_branch(self):
 
363
        """Return the branch we are bound to.
 
364
        
 
365
        :return: Either a Branch, or None
 
366
        """
 
367
        return None
 
368
 
 
369
    def get_revision_delta(self, revno):
 
370
        """Return the delta for one revision.
 
371
 
 
372
        The delta is relative to its mainline predecessor, or the
 
373
        empty tree for revision 1.
 
374
        """
 
375
        assert isinstance(revno, int)
 
376
        rh = self.revision_history()
 
377
        if not (1 <= revno <= len(rh)):
 
378
            raise InvalidRevisionNumber(revno)
 
379
        return self.repository.get_revision_delta(rh[revno-1])
 
380
 
 
381
    def get_root_id(self):
 
382
        """Return the id of this branches root"""
 
383
        raise NotImplementedError(self.get_root_id)
 
384
 
 
385
    def print_file(self, file, revision_id):
 
386
        """Print `file` to stdout."""
 
387
        raise NotImplementedError(self.print_file)
 
388
 
 
389
    def append_revision(self, *revision_ids):
 
390
        raise NotImplementedError(self.append_revision)
 
391
 
 
392
    def set_revision_history(self, rev_history):
 
393
        raise NotImplementedError(self.set_revision_history)
 
394
 
 
395
    def revision_history(self):
 
396
        """Return sequence of revision hashes on to this branch."""
 
397
        raise NotImplementedError(self.revision_history)
 
398
 
 
399
    def revno(self):
 
400
        """Return current revision number for this branch.
 
401
 
 
402
        That is equivalent to the number of revisions committed to
 
403
        this branch.
 
404
        """
 
405
        return len(self.revision_history())
 
406
 
 
407
    def unbind(self):
 
408
        """Older format branches cannot bind or unbind."""
 
409
        raise errors.UpgradeRequired(self.base)
 
410
 
 
411
    def last_revision(self):
 
412
        """Return last revision id, or None"""
 
413
        ph = self.revision_history()
 
414
        if ph:
 
415
            return ph[-1]
 
416
        else:
 
417
            return None
 
418
 
 
419
    def missing_revisions(self, other, stop_revision=None):
 
420
        """Return a list of new revisions that would perfectly fit.
 
421
        
 
422
        If self and other have not diverged, return a list of the revisions
 
423
        present in other, but missing from self.
 
424
        """
 
425
        self_history = self.revision_history()
 
426
        self_len = len(self_history)
 
427
        other_history = other.revision_history()
 
428
        other_len = len(other_history)
 
429
        common_index = min(self_len, other_len) -1
 
430
        if common_index >= 0 and \
 
431
            self_history[common_index] != other_history[common_index]:
 
432
            raise DivergedBranches(self, other)
 
433
 
 
434
        if stop_revision is None:
 
435
            stop_revision = other_len
 
436
        else:
 
437
            assert isinstance(stop_revision, int)
 
438
            if stop_revision > other_len:
 
439
                raise errors.NoSuchRevision(self, stop_revision)
 
440
        return other_history[self_len:stop_revision]
 
441
 
 
442
    def update_revisions(self, other, stop_revision=None):
 
443
        """Pull in new perfect-fit revisions.
 
444
 
 
445
        :param other: Another Branch to pull from
 
446
        :param stop_revision: Updated until the given revision
 
447
        :return: None
 
448
        """
 
449
        raise NotImplementedError(self.update_revisions)
 
450
 
 
451
    def revision_id_to_revno(self, revision_id):
 
452
        """Given a revision id, return its revno"""
 
453
        if revision_id is None:
 
454
            return 0
 
455
        history = self.revision_history()
 
456
        try:
 
457
            return history.index(revision_id) + 1
 
458
        except ValueError:
 
459
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
460
 
 
461
    def get_rev_id(self, revno, history=None):
 
462
        """Find the revision id of the specified revno."""
 
463
        if revno == 0:
 
464
            return None
 
465
        if history is None:
 
466
            history = self.revision_history()
 
467
        if revno <= 0 or revno > len(history):
 
468
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
469
        return history[revno - 1]
 
470
 
 
471
    def pull(self, source, overwrite=False, stop_revision=None):
 
472
        raise NotImplementedError(self.pull)
 
473
 
 
474
    def basis_tree(self):
 
475
        """Return `Tree` object for last revision."""
 
476
        return self.repository.revision_tree(self.last_revision())
 
477
 
 
478
    def rename_one(self, from_rel, to_rel):
 
479
        """Rename one file.
 
480
 
 
481
        This can change the directory or the filename or both.
 
482
        """
 
483
        raise NotImplementedError(self.rename_one)
 
484
 
 
485
    def move(self, from_paths, to_name):
 
486
        """Rename files.
 
487
 
 
488
        to_name must exist as a versioned directory.
 
489
 
 
490
        If to_name exists and is a directory, the files are moved into
 
491
        it, keeping their old names.  If it is a directory, 
 
492
 
 
493
        Note that to_name is only the last component of the new name;
 
494
        this doesn't change the directory.
 
495
 
 
496
        This returns a list of (from_path, to_path) pairs for each
 
497
        entry that is moved.
 
498
        """
 
499
        raise NotImplementedError(self.move)
 
500
 
 
501
    def get_parent(self):
 
502
        """Return the parent location of the branch.
 
503
 
 
504
        This is the default location for push/pull/missing.  The usual
 
505
        pattern is that the user can override it by specifying a
 
506
        location.
 
507
        """
 
508
        raise NotImplementedError(self.get_parent)
 
509
 
 
510
    def get_submit_branch(self):
 
511
        """Return the submit location of the branch.
 
512
 
 
513
        This is the default location for bundle.  The usual
 
514
        pattern is that the user can override it by specifying a
 
515
        location.
 
516
        """
 
517
        return self.get_config().get_user_option('submit_branch')
 
518
 
 
519
    def set_submit_branch(self, location):
 
520
        """Return the submit location of the branch.
 
521
 
 
522
        This is the default location for bundle.  The usual
 
523
        pattern is that the user can override it by specifying a
 
524
        location.
 
525
        """
 
526
        self.get_config().set_user_option('submit_branch', location)
 
527
 
 
528
    def get_push_location(self):
 
529
        """Return the None or the location to push this branch to."""
 
530
        raise NotImplementedError(self.get_push_location)
 
531
 
 
532
    def set_push_location(self, location):
 
533
        """Set a new push location for this branch."""
 
534
        raise NotImplementedError(self.set_push_location)
 
535
 
 
536
    def set_parent(self, url):
 
537
        raise NotImplementedError(self.set_parent)
 
538
 
 
539
    @needs_write_lock
 
540
    def update(self):
 
541
        """Synchronise this branch with the master branch if any. 
 
542
 
 
543
        :return: None or the last_revision pivoted out during the update.
 
544
        """
 
545
        return None
 
546
 
 
547
    def check_revno(self, revno):
 
548
        """\
 
549
        Check whether a revno corresponds to any revision.
 
550
        Zero (the NULL revision) is considered valid.
 
551
        """
 
552
        if revno != 0:
 
553
            self.check_real_revno(revno)
 
554
            
 
555
    def check_real_revno(self, revno):
 
556
        """\
 
557
        Check whether a revno corresponds to a real revision.
 
558
        Zero (the NULL revision) is considered invalid
 
559
        """
 
560
        if revno < 1 or revno > self.revno():
 
561
            raise InvalidRevisionNumber(revno)
 
562
 
 
563
    @needs_read_lock
 
564
    def clone(self, *args, **kwargs):
 
565
        """Clone this branch into to_bzrdir preserving all semantic values.
 
566
        
 
567
        revision_id: if not None, the revision history in the new branch will
 
568
                     be truncated to end with revision_id.
 
569
        """
 
570
        # for API compatibility, until 0.8 releases we provide the old api:
 
571
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
572
        # after 0.8 releases, the *args and **kwargs should be changed:
 
573
        # def clone(self, to_bzrdir, revision_id=None):
 
574
        if (kwargs.get('to_location', None) or
 
575
            kwargs.get('revision', None) or
 
576
            kwargs.get('basis_branch', None) or
 
577
            (len(args) and isinstance(args[0], basestring))):
 
578
            # backwards compatibility api:
 
579
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
580
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
581
            # get basis_branch
 
582
            if len(args) > 2:
 
583
                basis_branch = args[2]
 
584
            else:
 
585
                basis_branch = kwargs.get('basis_branch', None)
 
586
            if basis_branch:
 
587
                basis = basis_branch.bzrdir
 
588
            else:
 
589
                basis = None
 
590
            # get revision
 
591
            if len(args) > 1:
 
592
                revision_id = args[1]
 
593
            else:
 
594
                revision_id = kwargs.get('revision', None)
 
595
            # get location
 
596
            if len(args):
 
597
                url = args[0]
 
598
            else:
 
599
                # no default to raise if not provided.
 
600
                url = kwargs.get('to_location')
 
601
            return self.bzrdir.clone(url,
 
602
                                     revision_id=revision_id,
 
603
                                     basis=basis).open_branch()
 
604
        # new cleaner api.
 
605
        # generate args by hand 
 
606
        if len(args) > 1:
 
607
            revision_id = args[1]
 
608
        else:
 
609
            revision_id = kwargs.get('revision_id', None)
 
610
        if len(args):
 
611
            to_bzrdir = args[0]
 
612
        else:
 
613
            # no default to raise if not provided.
 
614
            to_bzrdir = kwargs.get('to_bzrdir')
 
615
        result = self._format.initialize(to_bzrdir)
 
616
        self.copy_content_into(result, revision_id=revision_id)
 
617
        return  result
 
618
 
 
619
    @needs_read_lock
 
620
    def sprout(self, to_bzrdir, revision_id=None):
 
621
        """Create a new line of development from the branch, into to_bzrdir.
 
622
        
 
623
        revision_id: if not None, the revision history in the new branch will
 
624
                     be truncated to end with revision_id.
 
625
        """
 
626
        result = self._format.initialize(to_bzrdir)
 
627
        self.copy_content_into(result, revision_id=revision_id)
 
628
        result.set_parent(self.bzrdir.root_transport.base)
 
629
        return result
 
630
 
 
631
    @needs_read_lock
 
632
    def copy_content_into(self, destination, revision_id=None):
 
633
        """Copy the content of self into destination.
 
634
 
 
635
        revision_id: if not None, the revision history in the new branch will
 
636
                     be truncated to end with revision_id.
 
637
        """
 
638
        new_history = self.revision_history()
 
639
        if revision_id is not None:
 
640
            try:
 
641
                new_history = new_history[:new_history.index(revision_id) + 1]
 
642
            except ValueError:
 
643
                rev = self.repository.get_revision(revision_id)
 
644
                new_history = rev.get_history(self.repository)[1:]
 
645
        destination.set_revision_history(new_history)
 
646
        try:
 
647
            parent = self.get_parent()
 
648
        except errors.InaccessibleParent, e:
 
649
            mutter('parent was not accessible to copy: %s', e)
 
650
        else:
 
651
            if parent:
 
652
                destination.set_parent(parent)
 
653
 
 
654
    @needs_read_lock
 
655
    def check(self):
 
656
        """Check consistency of the branch.
 
657
 
 
658
        In particular this checks that revisions given in the revision-history
 
659
        do actually match up in the revision graph, and that they're all 
 
660
        present in the repository.
 
661
        
 
662
        Callers will typically also want to check the repository.
 
663
 
 
664
        :return: A BranchCheckResult.
 
665
        """
 
666
        mainline_parent_id = None
 
667
        for revision_id in self.revision_history():
 
668
            try:
 
669
                revision = self.repository.get_revision(revision_id)
 
670
            except errors.NoSuchRevision, e:
 
671
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
 
672
                            % revision_id)
 
673
            # In general the first entry on the revision history has no parents.
 
674
            # But it's not illegal for it to have parents listed; this can happen
 
675
            # in imports from Arch when the parents weren't reachable.
 
676
            if mainline_parent_id is not None:
 
677
                if mainline_parent_id not in revision.parent_ids:
 
678
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
 
679
                                        "parents of {%s}"
 
680
                                        % (mainline_parent_id, revision_id))
 
681
            mainline_parent_id = revision_id
 
682
        return BranchCheckResult(self)
 
683
 
 
684
    def _get_checkout_format(self):
 
685
        """Return the most suitable metadir for a checkout of this branch.
 
686
        Weaves are used if this branch's repostory uses weaves.
 
687
        """
 
688
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
 
689
            from bzrlib import repository
 
690
            format = bzrdir.BzrDirMetaFormat1()
 
691
            format.repository_format = repository.RepositoryFormat7()
 
692
        else:
 
693
            format = self.repository.bzrdir.cloning_metadir()
 
694
        return format
 
695
 
 
696
    def create_checkout(self, to_location, revision_id=None, 
 
697
                        lightweight=False):
 
698
        """Create a checkout of a branch.
 
699
        
 
700
        :param to_location: The url to produce the checkout at
 
701
        :param revision_id: The revision to check out
 
702
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
703
        produce a bound branch (heavyweight checkout)
 
704
        :return: The tree of the created checkout
 
705
        """
 
706
        t = transport.get_transport(to_location)
 
707
        try:
 
708
            t.mkdir('.')
 
709
        except errors.FileExists:
 
710
            pass
 
711
        if lightweight:
 
712
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
713
            BranchReferenceFormat().initialize(checkout, self)
 
714
        else:
 
715
            format = self._get_checkout_format()
 
716
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
717
                to_location, force_new_tree=False, format=format)
 
718
            checkout = checkout_branch.bzrdir
 
719
            checkout_branch.bind(self)
 
720
            # pull up to the specified revision_id to set the initial 
 
721
            # branch tip correctly, and seed it with history.
 
722
            checkout_branch.pull(self, stop_revision=revision_id)
 
723
        return checkout.create_workingtree(revision_id)
 
724
 
 
725
    def set_tag(self, tag_name, tag_target):
 
726
        self._tag_store.set_tag(tag_name, tag_target)
 
727
 
 
728
    def lookup_tag(self, tag_name):
 
729
        return self._tag_store.lookup_tag(tag_name)
 
730
 
 
731
    def get_tag_dict(self):
 
732
        return self._tag_store.get_tag_dict()
 
733
 
 
734
    def _set_tag_dict(self, new_dict):
 
735
        return self._tag_store._set_tag_dict(new_dict)
 
736
 
 
737
    def supports_tags(self):
 
738
        return self._tag_store.supports_tags()
 
739
 
 
740
    def copy_tags_to(self, to_repository):
 
741
        """Copy tags to another repository.
 
742
 
 
743
        Subclasses should not override this, but rather customize copying 
 
744
        through the InterRepository mechanism.
 
745
        """
 
746
        return InterRepository.get(self, to_repository).copy_tags()
 
747
 
 
748
 
 
749
class BranchFormat(object):
 
750
    """An encapsulation of the initialization and open routines for a format.
 
751
 
 
752
    Formats provide three things:
 
753
     * An initialization routine,
 
754
     * a format string,
 
755
     * an open routine.
 
756
 
 
757
    Formats are placed in an dict by their format string for reference 
 
758
    during branch opening. Its not required that these be instances, they
 
759
    can be classes themselves with class methods - it simply depends on 
 
760
    whether state is needed for a given format or not.
 
761
 
 
762
    Once a format is deprecated, just deprecate the initialize and open
 
763
    methods on the format class. Do not deprecate the object, as the 
 
764
    object will be created every time regardless.
 
765
    """
 
766
 
 
767
    _default_format = None
 
768
    """The default format used for new branches."""
 
769
 
 
770
    _formats = {}
 
771
    """The known formats."""
 
772
 
 
773
    @classmethod
 
774
    def find_format(klass, a_bzrdir):
 
775
        """Return the format for the branch object in a_bzrdir."""
 
776
        try:
 
777
            transport = a_bzrdir.get_branch_transport(None)
 
778
            format_string = transport.get("format").read()
 
779
            return klass._formats[format_string]
 
780
        except NoSuchFile:
 
781
            raise NotBranchError(path=transport.base)
 
782
        except KeyError:
 
783
            raise errors.UnknownFormatError(format=format_string)
 
784
 
 
785
    @classmethod
 
786
    def get_default_format(klass):
 
787
        """Return the current default format."""
 
788
        return klass._default_format
 
789
 
 
790
    def get_format_string(self):
 
791
        """Return the ASCII format string that identifies this format."""
 
792
        raise NotImplementedError(self.get_format_string)
 
793
 
 
794
    def get_format_description(self):
 
795
        """Return the short format description for this format."""
 
796
        raise NotImplementedError(self.get_format_string)
 
797
 
 
798
    def initialize(self, a_bzrdir):
 
799
        """Create a branch of this format in a_bzrdir."""
 
800
        raise NotImplementedError(self.initialize)
 
801
 
 
802
    def is_supported(self):
 
803
        """Is this format supported?
 
804
 
 
805
        Supported formats can be initialized and opened.
 
806
        Unsupported formats may not support initialization or committing or 
 
807
        some other features depending on the reason for not being supported.
 
808
        """
 
809
        return True
 
810
 
 
811
    def open(self, a_bzrdir, _found=False):
 
812
        """Return the branch object for a_bzrdir
 
813
 
 
814
        _found is a private parameter, do not use it. It is used to indicate
 
815
               if format probing has already be done.
 
816
        """
 
817
        raise NotImplementedError(self.open)
 
818
 
 
819
    @classmethod
 
820
    def register_format(klass, format):
 
821
        klass._formats[format.get_format_string()] = format
 
822
 
 
823
    @classmethod
 
824
    def set_default_format(klass, format):
 
825
        klass._default_format = format
 
826
 
 
827
    @classmethod
 
828
    def unregister_format(klass, format):
 
829
        assert klass._formats[format.get_format_string()] is format
 
830
        del klass._formats[format.get_format_string()]
 
831
 
 
832
    def __str__(self):
 
833
        return self.get_format_string().rstrip()
 
834
 
 
835
    def supports_tags(self):
 
836
        """True if this format supports tags stored in the branch"""
 
837
        return False  # by default
 
838
 
 
839
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
840
            lock_class):
 
841
        branch_transport = a_bzrdir.get_branch_transport(self)
 
842
        control_files = lockable_files.LockableFiles(branch_transport,
 
843
            lock_filename, lock_class)
 
844
        control_files.create_lock()
 
845
        control_files.lock_write()
 
846
        try:
 
847
            for filename, content in utf8_files:
 
848
                control_files.put_utf8(filename, content)
 
849
        finally:
 
850
            control_files.unlock()
 
851
 
 
852
 
 
853
class BzrBranchFormat4(BranchFormat):
 
854
    """Bzr branch format 4.
 
855
 
 
856
    This format has:
 
857
     - a revision-history file.
 
858
     - a branch-lock lock file [ to be shared with the bzrdir ]
 
859
    """
 
860
 
 
861
    def get_format_description(self):
 
862
        """See BranchFormat.get_format_description()."""
 
863
        return "Branch format 4"
 
864
 
 
865
    def initialize(self, a_bzrdir):
 
866
        """Create a branch of this format in a_bzrdir."""
 
867
        utf8_files = [('revision-history', ''),
 
868
                      ('branch-name', ''),
 
869
                      ]
 
870
        self._initialize_control_files(a_bzrdir, utf8_files,
 
871
             'branch-lock', lockable_files.TransportLock)
 
872
        return self.open(a_bzrdir, _found=True)
 
873
 
 
874
    def __init__(self):
 
875
        super(BzrBranchFormat4, self).__init__()
 
876
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
877
 
 
878
    def open(self, a_bzrdir, _found=False):
 
879
        """Return the branch object for a_bzrdir
 
880
 
 
881
        _found is a private parameter, do not use it. It is used to indicate
 
882
               if format probing has already be done.
 
883
        """
 
884
        if not _found:
 
885
            # we are being called directly and must probe.
 
886
            raise NotImplementedError
 
887
        return BzrBranch(_format=self,
 
888
                         _control_files=a_bzrdir._control_files,
 
889
                         a_bzrdir=a_bzrdir,
 
890
                         _repository=a_bzrdir.open_repository())
 
891
 
 
892
    def __str__(self):
 
893
        return "Bazaar-NG branch format 4"
 
894
 
 
895
 
 
896
class BzrBranchFormat5(BranchFormat):
 
897
    """Bzr branch format 5.
 
898
 
 
899
    This format has:
 
900
     - a revision-history file.
 
901
     - a format string
 
902
     - a lock dir guarding the branch itself
 
903
     - all of this stored in a branch/ subdirectory
 
904
     - works with shared repositories.
 
905
 
 
906
    This format is new in bzr 0.8.
 
907
    """
 
908
 
 
909
    def get_format_string(self):
 
910
        """See BranchFormat.get_format_string()."""
 
911
        return "Bazaar-NG branch format 5\n"
 
912
 
 
913
    def get_format_description(self):
 
914
        """See BranchFormat.get_format_description()."""
 
915
        return "Branch format 5"
 
916
        
 
917
    def initialize(self, a_bzrdir):
 
918
        """Create a branch of this format in a_bzrdir."""
 
919
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
920
        branch_transport = a_bzrdir.get_branch_transport(self)
 
921
        utf8_files = [('revision-history', ''),
 
922
                      ('branch-name', ''),
 
923
                      ]
 
924
        control_files = lockable_files.LockableFiles(branch_transport, 'lock',
 
925
                                                     lockdir.LockDir)
 
926
        control_files.create_lock()
 
927
        control_files.lock_write()
 
928
        control_files.put_utf8('format', self.get_format_string())
 
929
        try:
 
930
            for file, content in utf8_files:
 
931
                control_files.put_utf8(file, content)
 
932
        finally:
 
933
            control_files.unlock()
 
934
        return self.open(a_bzrdir, _found=True, )
 
935
 
 
936
    def __init__(self):
 
937
        super(BzrBranchFormat5, self).__init__()
 
938
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
939
 
 
940
    def open(self, a_bzrdir, _found=False):
 
941
        """Return the branch object for a_bzrdir
 
942
 
 
943
        _found is a private parameter, do not use it. It is used to indicate
 
944
               if format probing has already be done.
 
945
        """
 
946
        if not _found:
 
947
            format = BranchFormat.find_format(a_bzrdir)
 
948
            assert format.__class__ == self.__class__
 
949
        transport = a_bzrdir.get_branch_transport(None)
 
950
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
951
                                                     lockdir.LockDir)
 
952
        return BzrBranch5(_format=self,
 
953
                          _control_files=control_files,
 
954
                          a_bzrdir=a_bzrdir,
 
955
                          _repository=a_bzrdir.find_repository())
 
956
 
 
957
 
 
958
class BzrBranchFormatExperimental(BranchFormat):
 
959
    """Bzr experimental branch format
 
960
 
 
961
    This format has:
 
962
     - a revision-history file.
 
963
     - a format string
 
964
     - a lock dir guarding the branch itself
 
965
     - all of this stored in a branch/ subdirectory
 
966
     - works with shared repositories.
 
967
     - a tag dictionary in the branch
 
968
 
 
969
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
970
    only for testing.
 
971
    """
 
972
 
 
973
    def get_format_string(self):
 
974
        """See BranchFormat.get_format_string()."""
 
975
        return "Bazaar-NG branch format experimental\n"
 
976
 
 
977
    def get_format_description(self):
 
978
        """See BranchFormat.get_format_description()."""
 
979
        return "Experimental branch format"
 
980
        
 
981
    def initialize(self, a_bzrdir):
 
982
        """Create a branch of this format in a_bzrdir."""
 
983
        utf8_files = [('format', self.get_format_string()),
 
984
                      ('revision-history', ''),
 
985
                      ('branch-name', ''),
 
986
                      ('tags', ''),
 
987
                      ]
 
988
        self._initialize_control_files(a_bzrdir, utf8_files,
 
989
            'lock', lockdir.LockDir)
 
990
        return self.open(a_bzrdir, _found=True)
 
991
 
 
992
    def __init__(self):
 
993
        super(BzrBranchFormatExperimental, self).__init__()
 
994
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
995
 
 
996
    def open(self, a_bzrdir, _found=False):
 
997
        """Return the branch object for a_bzrdir
 
998
 
 
999
        _found is a private parameter, do not use it. It is used to indicate
 
1000
               if format probing has already be done.
 
1001
        """
 
1002
        if not _found:
 
1003
            format = BranchFormat.find_format(a_bzrdir)
 
1004
            assert format.__class__ == self.__class__
 
1005
        transport = a_bzrdir.get_branch_transport(None)
 
1006
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1007
                                                     lockdir.LockDir)
 
1008
        return BzrBranchExperimental(_format=self,
 
1009
                          _control_files=control_files,
 
1010
                          a_bzrdir=a_bzrdir,
 
1011
                          _repository=a_bzrdir.find_repository())
 
1012
 
 
1013
 
 
1014
class BranchReferenceFormat(BranchFormat):
 
1015
    """Bzr branch reference format.
 
1016
 
 
1017
    Branch references are used in implementing checkouts, they
 
1018
    act as an alias to the real branch which is at some other url.
 
1019
 
 
1020
    This format has:
 
1021
     - A location file
 
1022
     - a format string
 
1023
    """
 
1024
 
 
1025
    def get_format_string(self):
 
1026
        """See BranchFormat.get_format_string()."""
 
1027
        return "Bazaar-NG Branch Reference Format 1\n"
 
1028
 
 
1029
    def get_format_description(self):
 
1030
        """See BranchFormat.get_format_description()."""
 
1031
        return "Checkout reference format 1"
 
1032
        
 
1033
    def initialize(self, a_bzrdir, target_branch=None):
 
1034
        """Create a branch of this format in a_bzrdir."""
 
1035
        if target_branch is None:
 
1036
            # this format does not implement branch itself, thus the implicit
 
1037
            # creation contract must see it as uninitializable
 
1038
            raise errors.UninitializableFormat(self)
 
1039
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
1040
        branch_transport = a_bzrdir.get_branch_transport(self)
 
1041
        branch_transport.put_bytes('location',
 
1042
            target_branch.bzrdir.root_transport.base)
 
1043
        branch_transport.put_bytes('format', self.get_format_string())
 
1044
        return self.open(a_bzrdir, _found=True)
 
1045
 
 
1046
    def __init__(self):
 
1047
        super(BranchReferenceFormat, self).__init__()
 
1048
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1049
 
 
1050
    def _make_reference_clone_function(format, a_branch):
 
1051
        """Create a clone() routine for a branch dynamically."""
 
1052
        def clone(to_bzrdir, revision_id=None):
 
1053
            """See Branch.clone()."""
 
1054
            return format.initialize(to_bzrdir, a_branch)
 
1055
            # cannot obey revision_id limits when cloning a reference ...
 
1056
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
1057
            # emit some sort of warning/error to the caller ?!
 
1058
        return clone
 
1059
 
 
1060
    def open(self, a_bzrdir, _found=False):
 
1061
        """Return the branch that the branch reference in a_bzrdir points at.
 
1062
 
 
1063
        _found is a private parameter, do not use it. It is used to indicate
 
1064
               if format probing has already be done.
 
1065
        """
 
1066
        if not _found:
 
1067
            format = BranchFormat.find_format(a_bzrdir)
 
1068
            assert format.__class__ == self.__class__
 
1069
        transport = a_bzrdir.get_branch_transport(None)
 
1070
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
1071
        result = real_bzrdir.open_branch()
 
1072
        # this changes the behaviour of result.clone to create a new reference
 
1073
        # rather than a copy of the content of the branch.
 
1074
        # I did not use a proxy object because that needs much more extensive
 
1075
        # testing, and we are only changing one behaviour at the moment.
 
1076
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
1077
        # then this should be refactored to introduce a tested proxy branch
 
1078
        # and a subclass of that for use in overriding clone() and ....
 
1079
        # - RBC 20060210
 
1080
        result.clone = self._make_reference_clone_function(result)
 
1081
        return result
 
1082
 
 
1083
 
 
1084
# formats which have no format string are not discoverable
 
1085
# and not independently creatable, so are not registered.
 
1086
__default_format = BzrBranchFormat5()
 
1087
BranchFormat.register_format(__default_format)
 
1088
BranchFormat.register_format(BranchReferenceFormat())
 
1089
BranchFormat.set_default_format(__default_format)
 
1090
_legacy_formats = [BzrBranchFormat4(),
 
1091
                   ]
 
1092
 
 
1093
class BzrBranch(Branch):
 
1094
    """A branch stored in the actual filesystem.
 
1095
 
 
1096
    Note that it's "local" in the context of the filesystem; it doesn't
 
1097
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
1098
    it's writable, and can be accessed via the normal filesystem API.
 
1099
    """
 
1100
    
 
1101
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
 
1102
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
1103
                 _control_files=None, a_bzrdir=None, _repository=None):
 
1104
        """Create new branch object at a particular location.
 
1105
 
 
1106
        transport -- A Transport object, defining how to access files.
 
1107
        
 
1108
        init -- If True, create new control files in a previously
 
1109
             unversioned directory.  If False, the branch must already
 
1110
             be versioned.
 
1111
 
 
1112
        relax_version_check -- If true, the usual check for the branch
 
1113
            version is not applied.  This is intended only for
 
1114
            upgrade/recovery type use; it's not guaranteed that
 
1115
            all operations will work on old format branches.
 
1116
        """
 
1117
        if a_bzrdir is None:
 
1118
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
1119
        else:
 
1120
            self.bzrdir = a_bzrdir
 
1121
        self._transport = self.bzrdir.transport.clone('..')
 
1122
        self._base = self._transport.base
 
1123
        self._format = _format
 
1124
        if _control_files is None:
 
1125
            raise ValueError('BzrBranch _control_files is None')
 
1126
        self.control_files = _control_files
 
1127
        if deprecated_passed(init):
 
1128
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
1129
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
1130
                 DeprecationWarning,
 
1131
                 stacklevel=2)
 
1132
            if init:
 
1133
                # this is slower than before deprecation, oh well never mind.
 
1134
                # -> its deprecated.
 
1135
                self._initialize(transport.base)
 
1136
        self._check_format(_format)
 
1137
        if deprecated_passed(relax_version_check):
 
1138
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
1139
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
1140
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
 
1141
                 "open() method.",
 
1142
                 DeprecationWarning,
 
1143
                 stacklevel=2)
 
1144
            if (not relax_version_check
 
1145
                and not self._format.is_supported()):
 
1146
                raise errors.UnsupportedFormatError(format=fmt)
 
1147
        if deprecated_passed(transport):
 
1148
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
1149
                 "parameter is deprecated as of bzr 0.8. "
 
1150
                 "Please use Branch.open, or bzrdir.open_branch().",
 
1151
                 DeprecationWarning,
 
1152
                 stacklevel=2)
 
1153
        self.repository = _repository
 
1154
 
 
1155
    def __str__(self):
 
1156
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
1157
 
 
1158
    __repr__ = __str__
 
1159
 
 
1160
    def _get_base(self):
 
1161
        return self._base
 
1162
 
 
1163
    base = property(_get_base, doc="The URL for the root of this branch.")
 
1164
 
 
1165
    def _finish_transaction(self):
 
1166
        """Exit the current transaction."""
 
1167
        return self.control_files._finish_transaction()
 
1168
 
 
1169
    def get_transaction(self):
 
1170
        """Return the current active transaction.
 
1171
 
 
1172
        If no transaction is active, this returns a passthrough object
 
1173
        for which all data is immediately flushed and no caching happens.
 
1174
        """
 
1175
        # this is an explicit function so that we can do tricky stuff
 
1176
        # when the storage in rev_storage is elsewhere.
 
1177
        # we probably need to hook the two 'lock a location' and 
 
1178
        # 'have a transaction' together more delicately, so that
 
1179
        # we can have two locks (branch and storage) and one transaction
 
1180
        # ... and finishing the transaction unlocks both, but unlocking
 
1181
        # does not. - RBC 20051121
 
1182
        return self.control_files.get_transaction()
 
1183
 
 
1184
    def _set_transaction(self, transaction):
 
1185
        """Set a new active transaction."""
 
1186
        return self.control_files._set_transaction(transaction)
 
1187
 
 
1188
    def abspath(self, name):
 
1189
        """See Branch.abspath."""
 
1190
        return self.control_files._transport.abspath(name)
 
1191
 
 
1192
    def _check_format(self, format):
 
1193
        """Identify the branch format if needed.
 
1194
 
 
1195
        The format is stored as a reference to the format object in
 
1196
        self._format for code that needs to check it later.
 
1197
 
 
1198
        The format parameter is either None or the branch format class
 
1199
        used to open this branch.
 
1200
 
 
1201
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
 
1202
        """
 
1203
        if format is None:
 
1204
            format = BranchFormat.find_format(self.bzrdir)
 
1205
        self._format = format
 
1206
        mutter("got branch format %s", self._format)
 
1207
 
 
1208
    @needs_read_lock
 
1209
    def get_root_id(self):
 
1210
        """See Branch.get_root_id."""
 
1211
        tree = self.repository.revision_tree(self.last_revision())
 
1212
        return tree.inventory.root.file_id
 
1213
 
 
1214
    def is_locked(self):
 
1215
        return self.control_files.is_locked()
 
1216
 
 
1217
    def lock_write(self):
 
1218
        self.repository.lock_write()
 
1219
        try:
 
1220
            self.control_files.lock_write()
 
1221
        except:
 
1222
            self.repository.unlock()
 
1223
            raise
 
1224
 
 
1225
    def lock_read(self):
 
1226
        self.repository.lock_read()
 
1227
        try:
 
1228
            self.control_files.lock_read()
 
1229
        except:
 
1230
            self.repository.unlock()
 
1231
            raise
 
1232
 
 
1233
    def unlock(self):
 
1234
        # TODO: test for failed two phase locks. This is known broken.
 
1235
        try:
 
1236
            self.control_files.unlock()
 
1237
        finally:
 
1238
            self.repository.unlock()
 
1239
        
 
1240
    def peek_lock_mode(self):
 
1241
        if self.control_files._lock_count == 0:
 
1242
            return None
 
1243
        else:
 
1244
            return self.control_files._lock_mode
 
1245
 
 
1246
    def get_physical_lock_status(self):
 
1247
        return self.control_files.get_physical_lock_status()
 
1248
 
 
1249
    @needs_read_lock
 
1250
    def print_file(self, file, revision_id):
 
1251
        """See Branch.print_file."""
 
1252
        return self.repository.print_file(file, revision_id)
 
1253
 
 
1254
    @needs_write_lock
 
1255
    def append_revision(self, *revision_ids):
 
1256
        """See Branch.append_revision."""
 
1257
        for revision_id in revision_ids:
 
1258
            mutter("add {%s} to revision-history" % revision_id)
 
1259
        rev_history = self.revision_history()
 
1260
        rev_history.extend(revision_ids)
 
1261
        self.set_revision_history(rev_history)
 
1262
 
 
1263
    @needs_write_lock
 
1264
    def set_revision_history(self, rev_history):
 
1265
        """See Branch.set_revision_history."""
 
1266
        self.control_files.put_utf8(
 
1267
            'revision-history', '\n'.join(rev_history))
 
1268
        transaction = self.get_transaction()
 
1269
        history = transaction.map.find_revision_history()
 
1270
        if history is not None:
 
1271
            # update the revision history in the identity map.
 
1272
            history[:] = list(rev_history)
 
1273
            # this call is disabled because revision_history is 
 
1274
            # not really an object yet, and the transaction is for objects.
 
1275
            # transaction.register_dirty(history)
 
1276
        else:
 
1277
            transaction.map.add_revision_history(rev_history)
 
1278
            # this call is disabled because revision_history is 
 
1279
            # not really an object yet, and the transaction is for objects.
 
1280
            # transaction.register_clean(history)
 
1281
 
 
1282
    @needs_read_lock
 
1283
    def revision_history(self):
 
1284
        """See Branch.revision_history."""
 
1285
        transaction = self.get_transaction()
 
1286
        history = transaction.map.find_revision_history()
 
1287
        if history is not None:
 
1288
            # mutter("cache hit for revision-history in %s", self)
 
1289
            return list(history)
 
1290
        decode_utf8 = cache_utf8.decode
 
1291
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1292
                self.control_files.get('revision-history').readlines()]
 
1293
        transaction.map.add_revision_history(history)
 
1294
        # this call is disabled because revision_history is 
 
1295
        # not really an object yet, and the transaction is for objects.
 
1296
        # transaction.register_clean(history, precious=True)
 
1297
        return list(history)
 
1298
 
 
1299
    @needs_write_lock
 
1300
    def generate_revision_history(self, revision_id, last_rev=None, 
 
1301
        other_branch=None):
 
1302
        """Create a new revision history that will finish with revision_id.
 
1303
        
 
1304
        :param revision_id: the new tip to use.
 
1305
        :param last_rev: The previous last_revision. If not None, then this
 
1306
            must be a ancestory of revision_id, or DivergedBranches is raised.
 
1307
        :param other_branch: The other branch that DivergedBranches should
 
1308
            raise with respect to.
 
1309
        """
 
1310
        # stop_revision must be a descendant of last_revision
 
1311
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1312
        if last_rev is not None and last_rev not in stop_graph:
 
1313
            # our previous tip is not merged into stop_revision
 
1314
            raise errors.DivergedBranches(self, other_branch)
 
1315
        # make a new revision history from the graph
 
1316
        current_rev_id = revision_id
 
1317
        new_history = []
 
1318
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
 
1319
            new_history.append(current_rev_id)
 
1320
            current_rev_id_parents = stop_graph[current_rev_id]
 
1321
            try:
 
1322
                current_rev_id = current_rev_id_parents[0]
 
1323
            except IndexError:
 
1324
                current_rev_id = None
 
1325
        new_history.reverse()
 
1326
        self.set_revision_history(new_history)
 
1327
 
 
1328
    @needs_write_lock
 
1329
    def update_revisions(self, other, stop_revision=None):
 
1330
        """See Branch.update_revisions."""
 
1331
        other.lock_read()
 
1332
        try:
 
1333
            if stop_revision is None:
 
1334
                stop_revision = other.last_revision()
 
1335
                if stop_revision is None:
 
1336
                    # if there are no commits, we're done.
 
1337
                    return
 
1338
            # whats the current last revision, before we fetch [and change it
 
1339
            # possibly]
 
1340
            last_rev = self.last_revision()
 
1341
            # we fetch here regardless of whether we need to so that we pickup
 
1342
            # filled in ghosts.
 
1343
            self.fetch(other, stop_revision)
 
1344
            my_ancestry = self.repository.get_ancestry(last_rev)
 
1345
            if stop_revision in my_ancestry:
 
1346
                # last_revision is a descendant of stop_revision
 
1347
                return
 
1348
            self.generate_revision_history(stop_revision, last_rev=last_rev,
 
1349
                other_branch=other)
 
1350
        finally:
 
1351
            other.unlock()
 
1352
 
 
1353
    def basis_tree(self):
 
1354
        """See Branch.basis_tree."""
 
1355
        return self.repository.revision_tree(self.last_revision())
 
1356
 
 
1357
    @deprecated_method(zero_eight)
 
1358
    def working_tree(self):
 
1359
        """Create a Working tree object for this branch."""
 
1360
 
 
1361
        from bzrlib.transport.local import LocalTransport
 
1362
        if (self.base.find('://') != -1 or 
 
1363
            not isinstance(self._transport, LocalTransport)):
 
1364
            raise NoWorkingTree(self.base)
 
1365
        return self.bzrdir.open_workingtree()
 
1366
 
 
1367
    @needs_write_lock
 
1368
    def pull(self, source, overwrite=False, stop_revision=None):
 
1369
        """See Branch.pull."""
 
1370
        source.lock_read()
 
1371
        try:
 
1372
            old_count = len(self.revision_history())
 
1373
            try:
 
1374
                self.update_revisions(source, stop_revision)
 
1375
            except DivergedBranches:
 
1376
                if not overwrite:
 
1377
                    raise
 
1378
            if overwrite:
 
1379
                self.set_revision_history(source.revision_history())
 
1380
            new_count = len(self.revision_history())
 
1381
            return new_count - old_count
 
1382
        finally:
 
1383
            source.unlock()
 
1384
 
 
1385
    def get_parent(self):
 
1386
        """See Branch.get_parent."""
 
1387
 
 
1388
        _locs = ['parent', 'pull', 'x-pull']
 
1389
        assert self.base[-1] == '/'
 
1390
        for l in _locs:
 
1391
            try:
 
1392
                parent = self.control_files.get(l).read().strip('\n')
 
1393
            except NoSuchFile:
 
1394
                continue
 
1395
            # This is an old-format absolute path to a local branch
 
1396
            # turn it into a url
 
1397
            if parent.startswith('/'):
 
1398
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1399
            try:
 
1400
                return urlutils.join(self.base[:-1], parent)
 
1401
            except errors.InvalidURLJoin, e:
 
1402
                raise errors.InaccessibleParent(parent, self.base)
 
1403
        return None
 
1404
 
 
1405
    def get_push_location(self):
 
1406
        """See Branch.get_push_location."""
 
1407
        push_loc = self.get_config().get_user_option('push_location')
 
1408
        return push_loc
 
1409
 
 
1410
    def set_push_location(self, location):
 
1411
        """See Branch.set_push_location."""
 
1412
        self.get_config().set_user_option(
 
1413
            'push_location', location,
 
1414
            store=_mod_config.STORE_LOCATION_NORECURSE)
 
1415
 
 
1416
    @needs_write_lock
 
1417
    def set_parent(self, url):
 
1418
        """See Branch.set_parent."""
 
1419
        # TODO: Maybe delete old location files?
 
1420
        # URLs should never be unicode, even on the local fs,
 
1421
        # FIXUP this and get_parent in a future branch format bump:
 
1422
        # read and rewrite the file, and have the new format code read
 
1423
        # using .get not .get_utf8. RBC 20060125
 
1424
        if url is None:
 
1425
            self.control_files._transport.delete('parent')
 
1426
        else:
 
1427
            if isinstance(url, unicode):
 
1428
                try: 
 
1429
                    url = url.encode('ascii')
 
1430
                except UnicodeEncodeError:
 
1431
                    raise bzrlib.errors.InvalidURL(url,
 
1432
                        "Urls must be 7-bit ascii, "
 
1433
                        "use bzrlib.urlutils.escape")
 
1434
                    
 
1435
            url = urlutils.relative_url(self.base, url)
 
1436
            self.control_files.put('parent', StringIO(url + '\n'))
 
1437
 
 
1438
    @deprecated_function(zero_nine)
 
1439
    def tree_config(self):
 
1440
        """DEPRECATED; call get_config instead.  
 
1441
        TreeConfig has become part of BranchConfig."""
 
1442
        return TreeConfig(self)
 
1443
 
 
1444
 
 
1445
class BzrBranch5(BzrBranch):
 
1446
    """A format 5 branch. This supports new features over plan branches.
 
1447
 
 
1448
    It has support for a master_branch which is the data for bound branches.
 
1449
    """
 
1450
 
 
1451
    def __init__(self,
 
1452
                 _format,
 
1453
                 _control_files,
 
1454
                 a_bzrdir,
 
1455
                 _repository):
 
1456
        super(BzrBranch5, self).__init__(_format=_format,
 
1457
                                         _control_files=_control_files,
 
1458
                                         a_bzrdir=a_bzrdir,
 
1459
                                         _repository=_repository)
 
1460
        
 
1461
    @needs_write_lock
 
1462
    def pull(self, source, overwrite=False, stop_revision=None):
 
1463
        """Updates branch.pull to be bound branch aware."""
 
1464
        bound_location = self.get_bound_location()
 
1465
        if source.base != bound_location:
 
1466
            # not pulling from master, so we need to update master.
 
1467
            master_branch = self.get_master_branch()
 
1468
            if master_branch:
 
1469
                master_branch.pull(source)
 
1470
                source = master_branch
 
1471
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
1472
 
 
1473
    def get_bound_location(self):
 
1474
        try:
 
1475
            return self.control_files.get_utf8('bound').read()[:-1]
 
1476
        except errors.NoSuchFile:
 
1477
            return None
 
1478
 
 
1479
    @needs_read_lock
 
1480
    def get_master_branch(self):
 
1481
        """Return the branch we are bound to.
 
1482
        
 
1483
        :return: Either a Branch, or None
 
1484
 
 
1485
        This could memoise the branch, but if thats done
 
1486
        it must be revalidated on each new lock.
 
1487
        So for now we just don't memoise it.
 
1488
        # RBC 20060304 review this decision.
 
1489
        """
 
1490
        bound_loc = self.get_bound_location()
 
1491
        if not bound_loc:
 
1492
            return None
 
1493
        try:
 
1494
            return Branch.open(bound_loc)
 
1495
        except (errors.NotBranchError, errors.ConnectionError), e:
 
1496
            raise errors.BoundBranchConnectionFailure(
 
1497
                    self, bound_loc, e)
 
1498
 
 
1499
    @needs_write_lock
 
1500
    def set_bound_location(self, location):
 
1501
        """Set the target where this branch is bound to.
 
1502
 
 
1503
        :param location: URL to the target branch
 
1504
        """
 
1505
        if location:
 
1506
            self.control_files.put_utf8('bound', location+'\n')
 
1507
        else:
 
1508
            try:
 
1509
                self.control_files._transport.delete('bound')
 
1510
            except NoSuchFile:
 
1511
                return False
 
1512
            return True
 
1513
 
 
1514
    @needs_write_lock
 
1515
    def bind(self, other):
 
1516
        """Bind this branch to the branch other.
 
1517
 
 
1518
        This does not push or pull data between the branches, though it does
 
1519
        check for divergence to raise an error when the branches are not
 
1520
        either the same, or one a prefix of the other. That behaviour may not
 
1521
        be useful, so that check may be removed in future.
 
1522
        
 
1523
        :param other: The branch to bind to
 
1524
        :type other: Branch
 
1525
        """
 
1526
        # TODO: jam 20051230 Consider checking if the target is bound
 
1527
        #       It is debatable whether you should be able to bind to
 
1528
        #       a branch which is itself bound.
 
1529
        #       Committing is obviously forbidden,
 
1530
        #       but binding itself may not be.
 
1531
        #       Since we *have* to check at commit time, we don't
 
1532
        #       *need* to check here
 
1533
 
 
1534
        # we want to raise diverged if:
 
1535
        # last_rev is not in the other_last_rev history, AND
 
1536
        # other_last_rev is not in our history, and do it without pulling
 
1537
        # history around
 
1538
        last_rev = self.last_revision()
 
1539
        if last_rev is not None:
 
1540
            other.lock_read()
 
1541
            try:
 
1542
                other_last_rev = other.last_revision()
 
1543
                if other_last_rev is not None:
 
1544
                    # neither branch is new, we have to do some work to
 
1545
                    # ascertain diversion.
 
1546
                    remote_graph = other.repository.get_revision_graph(
 
1547
                        other_last_rev)
 
1548
                    local_graph = self.repository.get_revision_graph(last_rev)
 
1549
                    if (last_rev not in remote_graph and
 
1550
                        other_last_rev not in local_graph):
 
1551
                        raise errors.DivergedBranches(self, other)
 
1552
            finally:
 
1553
                other.unlock()
 
1554
        self.set_bound_location(other.base)
 
1555
 
 
1556
    @needs_write_lock
 
1557
    def unbind(self):
 
1558
        """If bound, unbind"""
 
1559
        return self.set_bound_location(None)
 
1560
 
 
1561
    @needs_write_lock
 
1562
    def update(self):
 
1563
        """Synchronise this branch with the master branch if any. 
 
1564
 
 
1565
        :return: None or the last_revision that was pivoted out during the
 
1566
                 update.
 
1567
        """
 
1568
        master = self.get_master_branch()
 
1569
        if master is not None:
 
1570
            old_tip = self.last_revision()
 
1571
            self.pull(master, overwrite=True)
 
1572
            if old_tip in self.repository.get_ancestry(self.last_revision()):
 
1573
                return None
 
1574
            return old_tip
 
1575
        return None
 
1576
 
 
1577
 
 
1578
class BzrBranchExperimental(BzrBranch5):
 
1579
 
 
1580
    # TODO: within a lock scope, we could keep the tags in memory...
 
1581
    
 
1582
    _tag_store_class = _BasicTagStore
 
1583
 
 
1584
 
 
1585
 
 
1586
 
 
1587
class BranchTestProviderAdapter(object):
 
1588
    """A tool to generate a suite testing multiple branch formats at once.
 
1589
 
 
1590
    This is done by copying the test once for each transport and injecting
 
1591
    the transport_server, transport_readonly_server, and branch_format
 
1592
    classes into each copy. Each copy is also given a new id() to make it
 
1593
    easy to identify.
 
1594
    """
 
1595
 
 
1596
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1597
        self._transport_server = transport_server
 
1598
        self._transport_readonly_server = transport_readonly_server
 
1599
        self._formats = formats
 
1600
    
 
1601
    def adapt(self, test):
 
1602
        result = TestSuite()
 
1603
        for branch_format, bzrdir_format in self._formats:
 
1604
            new_test = deepcopy(test)
 
1605
            new_test.transport_server = self._transport_server
 
1606
            new_test.transport_readonly_server = self._transport_readonly_server
 
1607
            new_test.bzrdir_format = bzrdir_format
 
1608
            new_test.branch_format = branch_format
 
1609
            def make_new_test_id():
 
1610
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
1611
                return lambda: new_id
 
1612
            new_test.id = make_new_test_id()
 
1613
            result.addTest(new_test)
 
1614
        return result
 
1615
 
 
1616
 
 
1617
class BranchCheckResult(object):
 
1618
    """Results of checking branch consistency.
 
1619
 
 
1620
    :see: Branch.check
 
1621
    """
 
1622
 
 
1623
    def __init__(self, branch):
 
1624
        self.branch = branch
 
1625
 
 
1626
    def report_results(self, verbose):
 
1627
        """Report the check results via trace.note.
 
1628
        
 
1629
        :param verbose: Requests more detailed display of what was checked,
 
1630
            if any.
 
1631
        """
 
1632
        note('checked branch %s format %s',
 
1633
             self.branch.base,
 
1634
             self.branch._format)
 
1635
 
 
1636
 
 
1637
######################################################################
 
1638
# predicates
 
1639
 
 
1640
 
 
1641
@deprecated_function(zero_eight)
 
1642
def is_control_file(*args, **kwargs):
 
1643
    """See bzrlib.workingtree.is_control_file."""
 
1644
    from bzrlib import workingtree
 
1645
    return workingtree.is_control_file(*args, **kwargs)