/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-21 05:34:56 UTC
  • mfrom: (2296 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2309.
  • Revision ID: mbp@sourcefrog.net-20070221053456-vyr6o0ehqnbetrvb
merge trunk, in particular new Branch6 changes

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