/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2007-03-25 08:59:56 UTC
  • mto: (2376.3.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2401.
  • Revision ID: robertc@robertcollins.net-20070325085956-my8jv7cifqzyltyz
New SmartServer hooks facility. There are two initial hooks documented
in bzrlib.transport.smart.SmartServerHooks. The two initial hooks allow
plugins to execute code upon server startup and shutdown.
(Robert Collins).

SmartServer in standalone mode will now close its listening socket
when it stops, rather than waiting for garbage collection. This primarily
fixes test suite hangs when a test tries to connect to a shutdown server.
It may also help improve behaviour when dealing with a server running
on a specific port (rather than dynamically assigned ports).
(Robert Collins)

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