/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

Make branch_implementations/test_http.py a little clearer.

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