/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-27 04:57:59 UTC
  • mto: This revision was merged to the branch mainline in revision 2401.
  • Revision ID: robertc@robertcollins.net-20070327045759-hb7j634wpm1ris7l
Add more debugging code to the smart server to debug pqm failures.

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