/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2007-02-15 01:23:29 UTC
  • mfrom: (2284 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2309.
  • Revision ID: mbp@sourcefrog.net-20070215012329-blt2xfhup97r6w8h
merge up from bzr.dev to get metadir changes

Show diffs side-by-side

added added

removed removed

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