~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2006-08-23 22:16:27 UTC
  • mto: This revision was merged to the branch mainline in revision 1955.
  • Revision ID: john@arbash-meinel.com-20060823221627-fc64105bb12ae770
Ghozzy: Fix Bzr's support of Active FTP (aftp://)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
from bzrlib.lazy_import import lazy_import
19
 
lazy_import(globals(), """
 
18
from copy import deepcopy
 
19
from cStringIO import StringIO
 
20
from unittest import TestSuite
 
21
from warnings import warn
 
22
 
 
23
import bzrlib
20
24
from bzrlib import (
21
25
        bzrdir,
22
26
        cache_utf8,
23
 
        config as _mod_config,
24
 
        debug,
25
27
        errors,
26
28
        lockdir,
27
 
        lockable_files,
28
 
        repository,
29
 
        revision as _mod_revision,
 
29
        osutils,
 
30
        revision,
30
31
        transport,
31
 
        tsort,
 
32
        tree,
32
33
        ui,
33
34
        urlutils,
34
35
        )
35
 
from bzrlib.config import BranchConfig
36
 
from bzrlib.tag import (
37
 
    BasicTags,
38
 
    DisabledTags,
39
 
    )
40
 
""")
41
 
 
 
36
from bzrlib.config import TreeConfig
42
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
43
 
from bzrlib.hooks import Hooks
44
 
from bzrlib.symbol_versioning import (
45
 
    deprecated_in,
46
 
    deprecated_method,
47
 
    )
48
 
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
 
38
import bzrlib.errors as errors
 
39
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
 
40
                           HistoryMissing, InvalidRevisionId, 
 
41
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
42
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
 
43
                           NotBranchError, UninitializableFormat, 
 
44
                           UnlistableStore, UnlistableBranch, 
 
45
                           )
 
46
from bzrlib.lockable_files import LockableFiles, TransportLock
 
47
from bzrlib.symbol_versioning import (deprecated_function,
 
48
                                      deprecated_method,
 
49
                                      DEPRECATED_PARAMETER,
 
50
                                      deprecated_passed,
 
51
                                      zero_eight, zero_nine,
 
52
                                      )
 
53
from bzrlib.trace import mutter, note
49
54
 
50
55
 
51
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
52
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
53
 
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
 
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
54
59
 
55
60
 
56
61
# TODO: Maybe include checks for common corruption of newlines, etc?
70
75
 
71
76
    base
72
77
        Base directory/url of the branch.
73
 
 
74
 
    hooks: An instance of BranchHooks.
75
78
    """
76
79
    # this is really an instance variable - FIXME move it there
77
80
    # - RBC 20060112
78
81
    base = None
79
82
 
80
 
    # override this to set the strategy for storing tags
81
 
    def _make_tags(self):
82
 
        return DisabledTags(self)
83
 
 
84
83
    def __init__(self, *ignored, **ignored_too):
85
 
        self.tags = self._make_tags()
86
 
        self._revision_history_cache = None
87
 
        self._revision_id_to_revno_cache = None
 
84
        raise NotImplementedError('The Branch class is abstract')
88
85
 
89
86
    def break_lock(self):
90
87
        """Break a lock if one is present from another instance.
101
98
            master.break_lock()
102
99
 
103
100
    @staticmethod
104
 
    def open(base, _unsupported=False, possible_transports=None):
 
101
    @deprecated_method(zero_eight)
 
102
    def open_downlevel(base):
 
103
        """Open a branch which may be of an old format."""
 
104
        return Branch.open(base, _unsupported=True)
 
105
        
 
106
    @staticmethod
 
107
    def open(base, _unsupported=False):
105
108
        """Open the branch rooted at base.
106
109
 
107
110
        For instance, if the branch is at URL/.bzr/branch,
108
111
        Branch.open(URL) -> a Branch instance.
109
112
        """
110
 
        control = bzrdir.BzrDir.open(base, _unsupported,
111
 
                                     possible_transports=possible_transports)
112
 
        return control.open_branch(_unsupported)
113
 
 
114
 
    @staticmethod
115
 
    def open_from_transport(transport, _unsupported=False):
116
 
        """Open the branch rooted at transport"""
117
 
        control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
118
 
        return control.open_branch(_unsupported)
119
 
 
120
 
    @staticmethod
121
 
    def open_containing(url, possible_transports=None):
 
113
        control = bzrdir.BzrDir.open(base, _unsupported)
 
114
        return control.open_branch(_unsupported)
 
115
 
 
116
    @staticmethod
 
117
    def open_containing(url):
122
118
        """Open an existing branch which contains url.
123
119
        
124
120
        This probes for a branch at url, and searches upwards from there.
129
125
        format, UnknownFormatError or UnsupportedFormatError are raised.
130
126
        If there is one, it is returned, along with the unused portion of url.
131
127
        """
132
 
        control, relpath = bzrdir.BzrDir.open_containing(url,
133
 
                                                         possible_transports)
 
128
        control, relpath = bzrdir.BzrDir.open_containing(url)
134
129
        return control.open_branch(), relpath
135
130
 
 
131
    @staticmethod
 
132
    @deprecated_function(zero_eight)
 
133
    def initialize(base):
 
134
        """Create a new working tree and branch, rooted at 'base' (url)
 
135
 
 
136
        NOTE: This will soon be deprecated in favour of creation
 
137
        through a BzrDir.
 
138
        """
 
139
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
 
140
 
 
141
    def setup_caching(self, cache_root):
 
142
        """Subclasses that care about caching should override this, and set
 
143
        up cached stores located under cache_root.
 
144
        """
 
145
        # seems to be unused, 2006-01-13 mbp
 
146
        warn('%s is deprecated' % self.setup_caching)
 
147
        self.cache_root = cache_root
 
148
 
136
149
    def get_config(self):
137
 
        return BranchConfig(self)
 
150
        return bzrlib.config.BranchConfig(self)
138
151
 
139
152
    def _get_nick(self):
140
153
        return self.get_config().get_nickname()
141
154
 
142
155
    def _set_nick(self, nick):
143
 
        self.get_config().set_user_option('nickname', nick, warn_masked=True)
 
156
        self.get_config().set_user_option('nickname', nick)
144
157
 
145
158
    nick = property(_get_nick, _set_nick)
146
159
 
163
176
    def get_physical_lock_status(self):
164
177
        raise NotImplementedError(self.get_physical_lock_status)
165
178
 
166
 
    @needs_read_lock
167
 
    def get_revision_id_to_revno_map(self):
168
 
        """Return the revision_id => dotted revno map.
169
 
 
170
 
        This will be regenerated on demand, but will be cached.
171
 
 
172
 
        :return: A dictionary mapping revision_id => dotted revno.
173
 
            This dictionary should not be modified by the caller.
174
 
        """
175
 
        if self._revision_id_to_revno_cache is not None:
176
 
            mapping = self._revision_id_to_revno_cache
177
 
        else:
178
 
            mapping = self._gen_revno_map()
179
 
            self._cache_revision_id_to_revno(mapping)
180
 
        # TODO: jam 20070417 Since this is being cached, should we be returning
181
 
        #       a copy?
182
 
        # I would rather not, and instead just declare that users should not
183
 
        # modify the return value.
184
 
        return mapping
185
 
 
186
 
    def _gen_revno_map(self):
187
 
        """Create a new mapping from revision ids to dotted revnos.
188
 
 
189
 
        Dotted revnos are generated based on the current tip in the revision
190
 
        history.
191
 
        This is the worker function for get_revision_id_to_revno_map, which
192
 
        just caches the return value.
193
 
 
194
 
        :return: A dictionary mapping revision_id => dotted revno.
195
 
        """
196
 
        last_revision = self.last_revision()
197
 
        revision_graph = repository._old_get_graph(self.repository,
198
 
            last_revision)
199
 
        merge_sorted_revisions = tsort.merge_sort(
200
 
            revision_graph,
201
 
            last_revision,
202
 
            None,
203
 
            generate_revno=True)
204
 
        revision_id_to_revno = dict((rev_id, revno)
205
 
                                    for seq_num, rev_id, depth, revno, end_of_merge
206
 
                                     in merge_sorted_revisions)
207
 
        return revision_id_to_revno
208
 
 
209
 
    def leave_lock_in_place(self):
210
 
        """Tell this branch object not to release the physical lock when this
211
 
        object is unlocked.
212
 
        
213
 
        If lock_write doesn't return a token, then this method is not supported.
214
 
        """
215
 
        self.control_files.leave_in_place()
216
 
 
217
 
    def dont_leave_lock_in_place(self):
218
 
        """Tell this branch object to release the physical lock when this
219
 
        object is unlocked, even if it didn't originally acquire it.
220
 
 
221
 
        If lock_write doesn't return a token, then this method is not supported.
222
 
        """
223
 
        self.control_files.dont_leave_in_place()
224
 
 
225
 
    @deprecated_method(deprecated_in((0, 16, 0)))
226
179
    def abspath(self, name):
227
180
        """Return absolute filename for something in the branch
228
181
        
263
216
        try:
264
217
            if last_revision is None:
265
218
                pb.update('get source history')
266
 
                last_revision = from_branch.last_revision()
267
 
                last_revision = _mod_revision.ensure_null(last_revision)
 
219
                from_history = from_branch.revision_history()
 
220
                if from_history:
 
221
                    last_revision = from_history[-1]
 
222
                else:
 
223
                    # no history in the source branch
 
224
                    last_revision = revision.NULL_REVISION
268
225
            return self.repository.fetch(from_branch.repository,
269
226
                                         revision_id=last_revision,
270
227
                                         pb=nested_pb)
281
238
        """
282
239
        return None
283
240
    
284
 
    def get_old_bound_location(self):
285
 
        """Return the URL of the branch we used to be bound to
286
 
        """
287
 
        raise errors.UpgradeRequired(self.base)
288
 
 
289
241
    def get_commit_builder(self, parents, config=None, timestamp=None, 
290
242
                           timezone=None, committer=None, revprops=None, 
291
243
                           revision_id=None):
303
255
        if config is None:
304
256
            config = self.get_config()
305
257
        
306
 
        return self.repository.get_commit_builder(self, parents, config,
 
258
        return self.repository.get_commit_builder(self, parents, config, 
307
259
            timestamp, timezone, committer, revprops, revision_id)
308
260
 
309
 
    def get_master_branch(self, possible_transports=None):
 
261
    def get_master_branch(self):
310
262
        """Return the branch we are bound to.
311
263
        
312
264
        :return: Either a Branch, or None
319
271
        The delta is relative to its mainline predecessor, or the
320
272
        empty tree for revision 1.
321
273
        """
 
274
        assert isinstance(revno, int)
322
275
        rh = self.revision_history()
323
276
        if not (1 <= revno <= len(rh)):
324
 
            raise errors.InvalidRevisionNumber(revno)
 
277
            raise InvalidRevisionNumber(revno)
325
278
        return self.repository.get_revision_delta(rh[revno-1])
326
279
 
 
280
    def get_root_id(self):
 
281
        """Return the id of this branches root"""
 
282
        raise NotImplementedError(self.get_root_id)
 
283
 
327
284
    def print_file(self, file, revision_id):
328
285
        """Print `file` to stdout."""
329
286
        raise NotImplementedError(self.print_file)
330
287
 
 
288
    def append_revision(self, *revision_ids):
 
289
        raise NotImplementedError(self.append_revision)
 
290
 
331
291
    def set_revision_history(self, rev_history):
332
292
        raise NotImplementedError(self.set_revision_history)
333
293
 
334
 
    def _cache_revision_history(self, rev_history):
335
 
        """Set the cached revision history to rev_history.
336
 
 
337
 
        The revision_history method will use this cache to avoid regenerating
338
 
        the revision history.
339
 
 
340
 
        This API is semi-public; it only for use by subclasses, all other code
341
 
        should consider it to be private.
342
 
        """
343
 
        self._revision_history_cache = rev_history
344
 
 
345
 
    def _cache_revision_id_to_revno(self, revision_id_to_revno):
346
 
        """Set the cached revision_id => revno map to revision_id_to_revno.
347
 
 
348
 
        This API is semi-public; it only for use by subclasses, all other code
349
 
        should consider it to be private.
350
 
        """
351
 
        self._revision_id_to_revno_cache = revision_id_to_revno
352
 
 
353
 
    def _clear_cached_state(self):
354
 
        """Clear any cached data on this branch, e.g. cached revision history.
355
 
 
356
 
        This means the next call to revision_history will need to call
357
 
        _gen_revision_history.
358
 
 
359
 
        This API is semi-public; it only for use by subclasses, all other code
360
 
        should consider it to be private.
361
 
        """
362
 
        self._revision_history_cache = None
363
 
        self._revision_id_to_revno_cache = None
364
 
 
365
 
    def _gen_revision_history(self):
366
 
        """Return sequence of revision hashes on to this branch.
367
 
        
368
 
        Unlike revision_history, this method always regenerates or rereads the
369
 
        revision history, i.e. it does not cache the result, so repeated calls
370
 
        may be expensive.
371
 
 
372
 
        Concrete subclasses should override this instead of revision_history so
373
 
        that subclasses do not need to deal with caching logic.
374
 
        
375
 
        This API is semi-public; it only for use by subclasses, all other code
376
 
        should consider it to be private.
377
 
        """
378
 
        raise NotImplementedError(self._gen_revision_history)
379
 
 
380
 
    @needs_read_lock
381
294
    def revision_history(self):
382
 
        """Return sequence of revision ids on this branch.
383
 
        
384
 
        This method will cache the revision history for as long as it is safe to
385
 
        do so.
386
 
        """
387
 
        if 'evil' in debug.debug_flags:
388
 
            mutter_callsite(3, "revision_history scales with history.")
389
 
        if self._revision_history_cache is not None:
390
 
            history = self._revision_history_cache
391
 
        else:
392
 
            history = self._gen_revision_history()
393
 
            self._cache_revision_history(history)
394
 
        return list(history)
 
295
        """Return sequence of revision hashes on to this branch."""
 
296
        raise NotImplementedError('revision_history is abstract')
395
297
 
396
298
    def revno(self):
397
299
        """Return current revision number for this branch.
399
301
        That is equivalent to the number of revisions committed to
400
302
        this branch.
401
303
        """
402
 
        return self.last_revision_info()[0]
 
304
        return len(self.revision_history())
403
305
 
404
306
    def unbind(self):
405
307
        """Older format branches cannot bind or unbind."""
406
308
        raise errors.UpgradeRequired(self.base)
407
309
 
408
 
    def set_append_revisions_only(self, enabled):
409
 
        """Older format branches are never restricted to append-only"""
410
 
        raise errors.UpgradeRequired(self.base)
411
 
 
412
310
    def last_revision(self):
413
 
        """Return last revision id, or NULL_REVISION."""
414
 
        return self.last_revision_info()[1]
415
 
 
416
 
    def last_revision_info(self):
417
 
        """Return information about the last revision.
418
 
 
419
 
        :return: A tuple (revno, last_revision_id).
420
 
        """
421
 
        rh = self.revision_history()
422
 
        revno = len(rh)
423
 
        if revno:
424
 
            return (revno, rh[-1])
 
311
        """Return last revision id, or None"""
 
312
        ph = self.revision_history()
 
313
        if ph:
 
314
            return ph[-1]
425
315
        else:
426
 
            return (0, _mod_revision.NULL_REVISION)
 
316
            return None
427
317
 
428
318
    def missing_revisions(self, other, stop_revision=None):
429
319
        """Return a list of new revisions that would perfectly fit.
438
328
        common_index = min(self_len, other_len) -1
439
329
        if common_index >= 0 and \
440
330
            self_history[common_index] != other_history[common_index]:
441
 
            raise errors.DivergedBranches(self, other)
 
331
            raise DivergedBranches(self, other)
442
332
 
443
333
        if stop_revision is None:
444
334
            stop_revision = other_len
445
335
        else:
 
336
            assert isinstance(stop_revision, int)
446
337
            if stop_revision > other_len:
447
338
                raise errors.NoSuchRevision(self, stop_revision)
448
339
        return other_history[self_len:stop_revision]
458
349
 
459
350
    def revision_id_to_revno(self, revision_id):
460
351
        """Given a revision id, return its revno"""
461
 
        if _mod_revision.is_null(revision_id):
 
352
        if revision_id is None:
462
353
            return 0
463
354
        history = self.revision_history()
464
355
        try:
465
356
            return history.index(revision_id) + 1
466
357
        except ValueError:
467
 
            raise errors.NoSuchRevision(self, revision_id)
 
358
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
468
359
 
469
360
    def get_rev_id(self, revno, history=None):
470
361
        """Find the revision id of the specified revno."""
471
362
        if revno == 0:
472
 
            return _mod_revision.NULL_REVISION
 
363
            return None
473
364
        if history is None:
474
365
            history = self.revision_history()
475
 
        if revno <= 0 or revno > len(history):
476
 
            raise errors.NoSuchRevision(self, revno)
 
366
        elif revno <= 0 or revno > len(history):
 
367
            raise bzrlib.errors.NoSuchRevision(self, revno)
477
368
        return history[revno - 1]
478
369
 
479
 
    def pull(self, source, overwrite=False, stop_revision=None,
480
 
             possible_transports=None):
481
 
        """Mirror source into this branch.
482
 
 
483
 
        This branch is considered to be 'local', having low latency.
484
 
 
485
 
        :returns: PullResult instance
486
 
        """
 
370
    def pull(self, source, overwrite=False, stop_revision=None):
487
371
        raise NotImplementedError(self.pull)
488
372
 
489
 
    def push(self, target, overwrite=False, stop_revision=None):
490
 
        """Mirror this branch into target.
491
 
 
492
 
        This branch is considered to be 'local', having low latency.
493
 
        """
494
 
        raise NotImplementedError(self.push)
495
 
 
496
373
    def basis_tree(self):
497
374
        """Return `Tree` object for last revision."""
498
375
        return self.repository.revision_tree(self.last_revision())
529
406
        """
530
407
        raise NotImplementedError(self.get_parent)
531
408
 
532
 
    def _set_config_location(self, name, url, config=None,
533
 
                             make_relative=False):
534
 
        if config is None:
535
 
            config = self.get_config()
536
 
        if url is None:
537
 
            url = ''
538
 
        elif make_relative:
539
 
            url = urlutils.relative_url(self.base, url)
540
 
        config.set_user_option(name, url, warn_masked=True)
541
 
 
542
 
    def _get_config_location(self, name, config=None):
543
 
        if config is None:
544
 
            config = self.get_config()
545
 
        location = config.get_user_option(name)
546
 
        if location == '':
547
 
            location = None
548
 
        return location
549
 
 
550
409
    def get_submit_branch(self):
551
410
        """Return the submit location of the branch.
552
411
 
563
422
        pattern is that the user can override it by specifying a
564
423
        location.
565
424
        """
566
 
        self.get_config().set_user_option('submit_branch', location,
567
 
            warn_masked=True)
568
 
 
569
 
    def get_public_branch(self):
570
 
        """Return the public location of the branch.
571
 
 
572
 
        This is is used by merge directives.
573
 
        """
574
 
        return self._get_config_location('public_branch')
575
 
 
576
 
    def set_public_branch(self, location):
577
 
        """Return the submit location of the branch.
578
 
 
579
 
        This is the default location for bundle.  The usual
580
 
        pattern is that the user can override it by specifying a
581
 
        location.
582
 
        """
583
 
        self._set_config_location('public_branch', location)
 
425
        self.get_config().set_user_option('submit_branch', location)
584
426
 
585
427
    def get_push_location(self):
586
428
        """Return the None or the location to push this branch to."""
587
 
        push_loc = self.get_config().get_user_option('push_location')
588
 
        return push_loc
 
429
        raise NotImplementedError(self.get_push_location)
589
430
 
590
431
    def set_push_location(self, location):
591
432
        """Set a new push location for this branch."""
616
457
        Zero (the NULL revision) is considered invalid
617
458
        """
618
459
        if revno < 1 or revno > self.revno():
619
 
            raise errors.InvalidRevisionNumber(revno)
 
460
            raise InvalidRevisionNumber(revno)
620
461
 
621
462
    @needs_read_lock
622
 
    def clone(self, to_bzrdir, revision_id=None):
 
463
    def clone(self, *args, **kwargs):
623
464
        """Clone this branch into to_bzrdir preserving all semantic values.
624
465
        
625
466
        revision_id: if not None, the revision history in the new branch will
626
467
                     be truncated to end with revision_id.
627
468
        """
 
469
        # for API compatibility, until 0.8 releases we provide the old api:
 
470
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
471
        # after 0.8 releases, the *args and **kwargs should be changed:
 
472
        # def clone(self, to_bzrdir, revision_id=None):
 
473
        if (kwargs.get('to_location', None) or
 
474
            kwargs.get('revision', None) or
 
475
            kwargs.get('basis_branch', None) or
 
476
            (len(args) and isinstance(args[0], basestring))):
 
477
            # backwards compatibility api:
 
478
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
479
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
480
            # get basis_branch
 
481
            if len(args) > 2:
 
482
                basis_branch = args[2]
 
483
            else:
 
484
                basis_branch = kwargs.get('basis_branch', None)
 
485
            if basis_branch:
 
486
                basis = basis_branch.bzrdir
 
487
            else:
 
488
                basis = None
 
489
            # get revision
 
490
            if len(args) > 1:
 
491
                revision_id = args[1]
 
492
            else:
 
493
                revision_id = kwargs.get('revision', None)
 
494
            # get location
 
495
            if len(args):
 
496
                url = args[0]
 
497
            else:
 
498
                # no default to raise if not provided.
 
499
                url = kwargs.get('to_location')
 
500
            return self.bzrdir.clone(url,
 
501
                                     revision_id=revision_id,
 
502
                                     basis=basis).open_branch()
 
503
        # new cleaner api.
 
504
        # generate args by hand 
 
505
        if len(args) > 1:
 
506
            revision_id = args[1]
 
507
        else:
 
508
            revision_id = kwargs.get('revision_id', None)
 
509
        if len(args):
 
510
            to_bzrdir = args[0]
 
511
        else:
 
512
            # no default to raise if not provided.
 
513
            to_bzrdir = kwargs.get('to_bzrdir')
628
514
        result = self._format.initialize(to_bzrdir)
629
515
        self.copy_content_into(result, revision_id=revision_id)
630
516
        return  result
641
527
        result.set_parent(self.bzrdir.root_transport.base)
642
528
        return result
643
529
 
644
 
    def _synchronize_history(self, destination, revision_id):
645
 
        """Synchronize last revision and revision history between branches.
646
 
 
647
 
        This version is most efficient when the destination is also a
648
 
        BzrBranch5, but works for BzrBranch6 as long as the revision
649
 
        history is the true lefthand parent history, and all of the revisions
650
 
        are in the destination's repository.  If not, set_revision_history
651
 
        will fail.
652
 
 
653
 
        :param destination: The branch to copy the history into
654
 
        :param revision_id: The revision-id to truncate history at.  May
655
 
          be None to copy complete history.
 
530
    @needs_read_lock
 
531
    def copy_content_into(self, destination, revision_id=None):
 
532
        """Copy the content of self into destination.
 
533
 
 
534
        revision_id: if not None, the revision history in the new branch will
 
535
                     be truncated to end with revision_id.
656
536
        """
657
 
        if revision_id == _mod_revision.NULL_REVISION:
658
 
            new_history = []
659
537
        new_history = self.revision_history()
660
 
        if revision_id is not None and new_history != []:
 
538
        if revision_id is not None:
661
539
            try:
662
540
                new_history = new_history[:new_history.index(revision_id) + 1]
663
541
            except ValueError:
664
542
                rev = self.repository.get_revision(revision_id)
665
543
                new_history = rev.get_history(self.repository)[1:]
666
544
        destination.set_revision_history(new_history)
667
 
 
668
 
    @needs_read_lock
669
 
    def copy_content_into(self, destination, revision_id=None):
670
 
        """Copy the content of self into destination.
671
 
 
672
 
        revision_id: if not None, the revision history in the new branch will
673
 
                     be truncated to end with revision_id.
674
 
        """
675
 
        self._synchronize_history(destination, revision_id)
676
545
        try:
677
546
            parent = self.get_parent()
678
547
        except errors.InaccessibleParent, e:
680
549
        else:
681
550
            if parent:
682
551
                destination.set_parent(parent)
683
 
        self.tags.merge_to(destination.tags)
684
552
 
685
553
    @needs_read_lock
686
554
    def check(self):
695
563
        :return: A BranchCheckResult.
696
564
        """
697
565
        mainline_parent_id = None
698
 
        last_revno, last_revision_id = self.last_revision_info()
699
 
        real_rev_history = list(self.repository.iter_reverse_revision_history(
700
 
                                last_revision_id))
701
 
        real_rev_history.reverse()
702
 
        if len(real_rev_history) != last_revno:
703
 
            raise errors.BzrCheckError('revno does not match len(mainline)'
704
 
                ' %s != %s' % (last_revno, len(real_rev_history)))
705
 
        # TODO: We should probably also check that real_rev_history actually
706
 
        #       matches self.revision_history()
707
 
        for revision_id in real_rev_history:
 
566
        for revision_id in self.revision_history():
708
567
            try:
709
568
                revision = self.repository.get_revision(revision_id)
710
569
            except errors.NoSuchRevision, e:
721
580
            mainline_parent_id = revision_id
722
581
        return BranchCheckResult(self)
723
582
 
724
 
    def _get_checkout_format(self):
725
 
        """Return the most suitable metadir for a checkout of this branch.
726
 
        Weaves are used if this branch's repository uses weaves.
727
 
        """
728
 
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
729
 
            from bzrlib.repofmt import weaverepo
730
 
            format = bzrdir.BzrDirMetaFormat1()
731
 
            format.repository_format = weaverepo.RepositoryFormat7()
732
 
        else:
733
 
            format = self.repository.bzrdir.checkout_metadir()
734
 
            format.set_branch_format(self._format)
735
 
        return format
736
 
 
737
 
    def create_checkout(self, to_location, revision_id=None,
738
 
                        lightweight=False, accelerator_tree=None,
739
 
                        hardlink=False):
 
583
    def create_checkout(self, to_location, revision_id=None, 
 
584
                        lightweight=False):
740
585
        """Create a checkout of a branch.
741
586
        
742
587
        :param to_location: The url to produce the checkout at
743
588
        :param revision_id: The revision to check out
744
589
        :param lightweight: If True, produce a lightweight checkout, otherwise,
745
590
        produce a bound branch (heavyweight checkout)
746
 
        :param accelerator_tree: A tree which can be used for retrieving file
747
 
            contents more quickly than the revision tree, i.e. a workingtree.
748
 
            The revision tree will be used for cases where accelerator_tree's
749
 
            content is different.
750
 
        :param hardlink: If true, hard-link files from accelerator_tree,
751
 
            where possible.
752
591
        :return: The tree of the created checkout
753
592
        """
754
 
        t = transport.get_transport(to_location)
755
 
        t.ensure_base()
756
593
        if lightweight:
757
 
            format = self._get_checkout_format()
758
 
            checkout = format.initialize_on_transport(t)
759
 
            from_branch = BranchReferenceFormat().initialize(checkout, self)
 
594
            t = transport.get_transport(to_location)
 
595
            try:
 
596
                t.mkdir('.')
 
597
            except errors.FileExists:
 
598
                pass
 
599
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
600
            BranchReferenceFormat().initialize(checkout, self)
760
601
        else:
761
 
            format = self._get_checkout_format()
762
602
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
763
 
                to_location, force_new_tree=False, format=format)
 
603
                to_location, force_new_tree=False)
764
604
            checkout = checkout_branch.bzrdir
765
605
            checkout_branch.bind(self)
766
 
            # pull up to the specified revision_id to set the initial 
767
 
            # branch tip correctly, and seed it with history.
768
 
            checkout_branch.pull(self, stop_revision=revision_id)
769
 
            from_branch=None
770
 
        tree = checkout.create_workingtree(revision_id,
771
 
                                           from_branch=from_branch,
772
 
                                           accelerator_tree=accelerator_tree,
773
 
                                           hardlink=hardlink)
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
 
    @needs_write_lock
787
 
    def reconcile(self, thorough=True):
788
 
        """Make sure the data stored in this branch is consistent."""
789
 
        from bzrlib.reconcile import BranchReconciler
790
 
        reconciler = BranchReconciler(self, thorough=thorough)
791
 
        reconciler.reconcile()
792
 
        return reconciler
793
 
 
794
 
    def reference_parent(self, file_id, path):
795
 
        """Return the parent branch for a tree-reference file_id
796
 
        :param file_id: The file_id of the tree reference
797
 
        :param path: The path of the file_id in the tree
798
 
        :return: A branch associated with the file_id
799
 
        """
800
 
        # FIXME should provide multiple branches, based on config
801
 
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
802
 
 
803
 
    def supports_tags(self):
804
 
        return self._format.supports_tags()
 
606
            if revision_id is not None:
 
607
                rh = checkout_branch.revision_history()
 
608
                new_rh = rh[:rh.index(revision_id) + 1]
 
609
                checkout_branch.set_revision_history(new_rh)
 
610
        return checkout.create_workingtree(revision_id)
805
611
 
806
612
 
807
613
class BranchFormat(object):
828
634
    _formats = {}
829
635
    """The known formats."""
830
636
 
831
 
    def __eq__(self, other):
832
 
        return self.__class__ is other.__class__
833
 
 
834
 
    def __ne__(self, other):
835
 
        return not (self == other)
836
 
 
837
637
    @classmethod
838
638
    def find_format(klass, a_bzrdir):
839
639
        """Return the format for the branch object in a_bzrdir."""
841
641
            transport = a_bzrdir.get_branch_transport(None)
842
642
            format_string = transport.get("format").read()
843
643
            return klass._formats[format_string]
844
 
        except errors.NoSuchFile:
845
 
            raise errors.NotBranchError(path=transport.base)
 
644
        except NoSuchFile:
 
645
            raise NotBranchError(path=transport.base)
846
646
        except KeyError:
847
 
            raise errors.UnknownFormatError(format=format_string, kind='branch')
 
647
            raise errors.UnknownFormatError(format=format_string)
848
648
 
849
649
    @classmethod
850
650
    def get_default_format(klass):
851
651
        """Return the current default format."""
852
652
        return klass._default_format
853
653
 
854
 
    def get_reference(self, a_bzrdir):
855
 
        """Get the target reference of the branch in a_bzrdir.
856
 
 
857
 
        format probing must have been completed before calling
858
 
        this method - it is assumed that the format of the branch
859
 
        in a_bzrdir is correct.
860
 
 
861
 
        :param a_bzrdir: The bzrdir to get the branch data from.
862
 
        :return: None if the branch is not a reference branch.
863
 
        """
864
 
        return None
865
 
 
866
 
    @classmethod
867
 
    def set_reference(self, a_bzrdir, to_branch):
868
 
        """Set the target reference of the branch in a_bzrdir.
869
 
 
870
 
        format probing must have been completed before calling
871
 
        this method - it is assumed that the format of the branch
872
 
        in a_bzrdir is correct.
873
 
 
874
 
        :param a_bzrdir: The bzrdir to set the branch reference for.
875
 
        :param to_branch: branch that the checkout is to reference
876
 
        """
877
 
        raise NotImplementedError(self.set_reference)
878
 
 
879
654
    def get_format_string(self):
880
655
        """Return the ASCII format string that identifies this format."""
881
656
        raise NotImplementedError(self.get_format_string)
882
657
 
883
658
    def get_format_description(self):
884
659
        """Return the short format description for this format."""
885
 
        raise NotImplementedError(self.get_format_description)
886
 
 
887
 
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
888
 
                           set_format=True):
889
 
        """Initialize a branch in a bzrdir, with specified files
890
 
 
891
 
        :param a_bzrdir: The bzrdir to initialize the branch in
892
 
        :param utf8_files: The files to create as a list of
893
 
            (filename, content) tuples
894
 
        :param set_format: If True, set the format with
895
 
            self.get_format_string.  (BzrBranch4 has its format set
896
 
            elsewhere)
897
 
        :return: a branch in this format
898
 
        """
899
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
900
 
        branch_transport = a_bzrdir.get_branch_transport(self)
901
 
        lock_map = {
902
 
            'metadir': ('lock', lockdir.LockDir),
903
 
            'branch4': ('branch-lock', lockable_files.TransportLock),
904
 
        }
905
 
        lock_name, lock_class = lock_map[lock_type]
906
 
        control_files = lockable_files.LockableFiles(branch_transport,
907
 
            lock_name, lock_class)
908
 
        control_files.create_lock()
909
 
        control_files.lock_write()
910
 
        if set_format:
911
 
            utf8_files += [('format', self.get_format_string())]
912
 
        try:
913
 
            for (filename, content) in utf8_files:
914
 
                branch_transport.put_bytes(
915
 
                    filename, content,
916
 
                    mode=control_files._file_mode)
917
 
        finally:
918
 
            control_files.unlock()
919
 
        return self.open(a_bzrdir, _found=True)
 
660
        raise NotImplementedError(self.get_format_string)
920
661
 
921
662
    def initialize(self, a_bzrdir):
922
663
        """Create a branch of this format in a_bzrdir."""
949
690
 
950
691
    @classmethod
951
692
    def unregister_format(klass, format):
 
693
        assert klass._formats[format.get_format_string()] is format
952
694
        del klass._formats[format.get_format_string()]
953
695
 
954
696
    def __str__(self):
955
697
        return self.get_format_string().rstrip()
956
698
 
957
 
    def supports_tags(self):
958
 
        """True if this format supports tags stored in the branch"""
959
 
        return False  # by default
960
 
 
961
 
 
962
 
class BranchHooks(Hooks):
963
 
    """A dictionary mapping hook name to a list of callables for branch hooks.
964
 
    
965
 
    e.g. ['set_rh'] Is the list of items to be called when the
966
 
    set_revision_history function is invoked.
967
 
    """
968
 
 
969
 
    def __init__(self):
970
 
        """Create the default hooks.
971
 
 
972
 
        These are all empty initially, because by default nothing should get
973
 
        notified.
974
 
        """
975
 
        Hooks.__init__(self)
976
 
        # Introduced in 0.15:
977
 
        # invoked whenever the revision history has been set
978
 
        # with set_revision_history. The api signature is
979
 
        # (branch, revision_history), and the branch will
980
 
        # be write-locked.
981
 
        self['set_rh'] = []
982
 
        # invoked after a push operation completes.
983
 
        # the api signature is
984
 
        # (push_result)
985
 
        # containing the members
986
 
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
987
 
        # where local is the local target branch or None, master is the target 
988
 
        # master branch, and the rest should be self explanatory. The source
989
 
        # is read locked and the target branches write locked. Source will
990
 
        # be the local low-latency branch.
991
 
        self['post_push'] = []
992
 
        # invoked after a pull operation completes.
993
 
        # the api signature is
994
 
        # (pull_result)
995
 
        # containing the members
996
 
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
997
 
        # where local is the local branch or None, master is the target 
998
 
        # master branch, and the rest should be self explanatory. The source
999
 
        # is read locked and the target branches write locked. The local
1000
 
        # branch is the low-latency branch.
1001
 
        self['post_pull'] = []
1002
 
        # invoked before a commit operation takes place.
1003
 
        # the api signature is
1004
 
        # (local, master, old_revno, old_revid, future_revno, future_revid,
1005
 
        #  tree_delta, future_tree).
1006
 
        # old_revid is NULL_REVISION for the first commit to a branch
1007
 
        # tree_delta is a TreeDelta object describing changes from the basis
1008
 
        # revision, hooks MUST NOT modify this delta
1009
 
        # future_tree is an in-memory tree obtained from
1010
 
        # CommitBuilder.revision_tree() and hooks MUST NOT modify this tree
1011
 
        self['pre_commit'] = []
1012
 
        # invoked after a commit operation completes.
1013
 
        # the api signature is 
1014
 
        # (local, master, old_revno, old_revid, new_revno, new_revid)
1015
 
        # old_revid is NULL_REVISION for the first commit to a branch.
1016
 
        self['post_commit'] = []
1017
 
        # invoked after a uncommit operation completes.
1018
 
        # the api signature is
1019
 
        # (local, master, old_revno, old_revid, new_revno, new_revid) where
1020
 
        # local is the local branch or None, master is the target branch,
1021
 
        # and an empty branch recieves new_revno of 0, new_revid of None.
1022
 
        self['post_uncommit'] = []
1023
 
        # Introduced in 1.4
1024
 
        # Invoked after the tip of a branch changes.
1025
 
        # the api signature is
1026
 
        # (params) where params is a ChangeBranchTipParams with the members
1027
 
        # (branch, old_revno, new_revno, old_revid, new_revid)
1028
 
        self['post_change_branch_tip'] = []
1029
 
 
1030
 
 
1031
 
# install the default hooks into the Branch class.
1032
 
Branch.hooks = BranchHooks()
1033
 
 
1034
 
 
1035
 
class ChangeBranchTipParams(object):
1036
 
    """Object holding parameters passed to *_change_branch_tip hooks.
1037
 
 
1038
 
    There are 5 fields that hooks may wish to access:
1039
 
 
1040
 
    :ivar branch: the branch being changed
1041
 
    :ivar old_revno: revision number before the change
1042
 
    :ivar new_revno: revision number after the change
1043
 
    :ivar old_revid: revision id before the change
1044
 
    :ivar new_revid: revision id after the change
1045
 
 
1046
 
    The revid fields are strings. The revno fields are integers.
1047
 
    """
1048
 
 
1049
 
    def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1050
 
        """Create a group of ChangeBranchTip parameters.
1051
 
 
1052
 
        :param branch: The branch being changed.
1053
 
        :param old_revno: Revision number before the change.
1054
 
        :param new_revno: Revision number after the change.
1055
 
        :param old_revid: Tip revision id before the change.
1056
 
        :param new_revid: Tip revision id after the change.
1057
 
        """
1058
 
        self.branch = branch
1059
 
        self.old_revno = old_revno
1060
 
        self.new_revno = new_revno
1061
 
        self.old_revid = old_revid
1062
 
        self.new_revid = new_revid
1063
 
 
1064
699
 
1065
700
class BzrBranchFormat4(BranchFormat):
1066
701
    """Bzr branch format 4.
1076
711
 
1077
712
    def initialize(self, a_bzrdir):
1078
713
        """Create a branch of this format in a_bzrdir."""
 
714
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
715
        branch_transport = a_bzrdir.get_branch_transport(self)
1079
716
        utf8_files = [('revision-history', ''),
1080
717
                      ('branch-name', ''),
1081
718
                      ]
1082
 
        return self._initialize_helper(a_bzrdir, utf8_files,
1083
 
                                       lock_type='branch4', set_format=False)
 
719
        control_files = LockableFiles(branch_transport, 'branch-lock',
 
720
                                      TransportLock)
 
721
        control_files.create_lock()
 
722
        control_files.lock_write()
 
723
        try:
 
724
            for file, content in utf8_files:
 
725
                control_files.put_utf8(file, content)
 
726
        finally:
 
727
            control_files.unlock()
 
728
        return self.open(a_bzrdir, _found=True)
1084
729
 
1085
730
    def __init__(self):
1086
731
        super(BzrBranchFormat4, self).__init__()
1127
772
        
1128
773
    def initialize(self, a_bzrdir):
1129
774
        """Create a branch of this format in a_bzrdir."""
 
775
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
776
        branch_transport = a_bzrdir.get_branch_transport(self)
1130
777
        utf8_files = [('revision-history', ''),
1131
778
                      ('branch-name', ''),
1132
779
                      ]
1133
 
        return self._initialize_helper(a_bzrdir, utf8_files)
 
780
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
 
781
        control_files.create_lock()
 
782
        control_files.lock_write()
 
783
        control_files.put_utf8('format', self.get_format_string())
 
784
        try:
 
785
            for file, content in utf8_files:
 
786
                control_files.put_utf8(file, content)
 
787
        finally:
 
788
            control_files.unlock()
 
789
        return self.open(a_bzrdir, _found=True, )
1134
790
 
1135
791
    def __init__(self):
1136
792
        super(BzrBranchFormat5, self).__init__()
1144
800
        """
1145
801
        if not _found:
1146
802
            format = BranchFormat.find_format(a_bzrdir)
1147
 
            if format.__class__ != self.__class__:
1148
 
                raise AssertionError("wrong format %r found for %r" %
1149
 
                    (format, self))
1150
 
        try:
1151
 
            transport = a_bzrdir.get_branch_transport(None)
1152
 
            control_files = lockable_files.LockableFiles(transport, 'lock',
1153
 
                                                         lockdir.LockDir)
1154
 
            return BzrBranch5(_format=self,
1155
 
                              _control_files=control_files,
1156
 
                              a_bzrdir=a_bzrdir,
1157
 
                              _repository=a_bzrdir.find_repository())
1158
 
        except errors.NoSuchFile:
1159
 
            raise errors.NotBranchError(path=transport.base)
1160
 
 
1161
 
 
1162
 
class BzrBranchFormat6(BzrBranchFormat5):
1163
 
    """Branch format with last-revision and tags.
1164
 
 
1165
 
    Unlike previous formats, this has no explicit revision history. Instead,
1166
 
    this just stores the last-revision, and the left-hand history leading
1167
 
    up to there is the history.
1168
 
 
1169
 
    This format was introduced in bzr 0.15
1170
 
    and became the default in 0.91.
1171
 
    """
1172
 
 
1173
 
    def get_format_string(self):
1174
 
        """See BranchFormat.get_format_string()."""
1175
 
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1176
 
 
1177
 
    def get_format_description(self):
1178
 
        """See BranchFormat.get_format_description()."""
1179
 
        return "Branch format 6"
1180
 
 
1181
 
    def initialize(self, a_bzrdir):
1182
 
        """Create a branch of this format in a_bzrdir."""
1183
 
        utf8_files = [('last-revision', '0 null:\n'),
1184
 
                      ('branch.conf', ''),
1185
 
                      ('tags', ''),
1186
 
                      ]
1187
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1188
 
 
1189
 
    def open(self, a_bzrdir, _found=False):
1190
 
        """Return the branch object for a_bzrdir
1191
 
 
1192
 
        _found is a private parameter, do not use it. It is used to indicate
1193
 
               if format probing has already be done.
1194
 
        """
1195
 
        if not _found:
1196
 
            format = BranchFormat.find_format(a_bzrdir)
1197
 
            if format.__class__ != self.__class__:
1198
 
                raise AssertionError("wrong format %r found for %r" %
1199
 
                    (format, self))
 
803
            assert format.__class__ == self.__class__
1200
804
        transport = a_bzrdir.get_branch_transport(None)
1201
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1202
 
                                                     lockdir.LockDir)
1203
 
        return BzrBranch6(_format=self,
 
805
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
 
806
        return BzrBranch5(_format=self,
1204
807
                          _control_files=control_files,
1205
808
                          a_bzrdir=a_bzrdir,
1206
809
                          _repository=a_bzrdir.find_repository())
1207
810
 
1208
 
    def supports_tags(self):
1209
 
        return True
 
811
    def __str__(self):
 
812
        return "Bazaar-NG Metadir branch format 5"
1210
813
 
1211
814
 
1212
815
class BranchReferenceFormat(BranchFormat):
1227
830
    def get_format_description(self):
1228
831
        """See BranchFormat.get_format_description()."""
1229
832
        return "Checkout reference format 1"
1230
 
 
1231
 
    def get_reference(self, a_bzrdir):
1232
 
        """See BranchFormat.get_reference()."""
1233
 
        transport = a_bzrdir.get_branch_transport(None)
1234
 
        return transport.get('location').read()
1235
 
 
1236
 
    def set_reference(self, a_bzrdir, to_branch):
1237
 
        """See BranchFormat.set_reference()."""
1238
 
        transport = a_bzrdir.get_branch_transport(None)
1239
 
        location = transport.put_bytes('location', to_branch.base)
1240
 
 
 
833
        
1241
834
    def initialize(self, a_bzrdir, target_branch=None):
1242
835
        """Create a branch of this format in a_bzrdir."""
1243
836
        if target_branch is None:
1246
839
            raise errors.UninitializableFormat(self)
1247
840
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
1248
841
        branch_transport = a_bzrdir.get_branch_transport(self)
1249
 
        branch_transport.put_bytes('location',
1250
 
            target_branch.bzrdir.root_transport.base)
1251
 
        branch_transport.put_bytes('format', self.get_format_string())
1252
 
        return self.open(
1253
 
            a_bzrdir, _found=True,
1254
 
            possible_transports=[target_branch.bzrdir.root_transport])
 
842
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
843
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
844
        branch_transport.put('format', StringIO(self.get_format_string()))
 
845
        return self.open(a_bzrdir, _found=True)
1255
846
 
1256
847
    def __init__(self):
1257
848
        super(BranchReferenceFormat, self).__init__()
1267
858
            # emit some sort of warning/error to the caller ?!
1268
859
        return clone
1269
860
 
1270
 
    def open(self, a_bzrdir, _found=False, location=None,
1271
 
             possible_transports=None):
 
861
    def open(self, a_bzrdir, _found=False):
1272
862
        """Return the branch that the branch reference in a_bzrdir points at.
1273
863
 
1274
864
        _found is a private parameter, do not use it. It is used to indicate
1276
866
        """
1277
867
        if not _found:
1278
868
            format = BranchFormat.find_format(a_bzrdir)
1279
 
            if format.__class__ != self.__class__:
1280
 
                raise AssertionError("wrong format %r found for %r" %
1281
 
                    (format, self))
1282
 
        if location is None:
1283
 
            location = self.get_reference(a_bzrdir)
1284
 
        real_bzrdir = bzrdir.BzrDir.open(
1285
 
            location, possible_transports=possible_transports)
 
869
            assert format.__class__ == self.__class__
 
870
        transport = a_bzrdir.get_branch_transport(None)
 
871
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
1286
872
        result = real_bzrdir.open_branch()
1287
873
        # this changes the behaviour of result.clone to create a new reference
1288
874
        # rather than a copy of the content of the branch.
1298
884
 
1299
885
# formats which have no format string are not discoverable
1300
886
# and not independently creatable, so are not registered.
1301
 
__format5 = BzrBranchFormat5()
1302
 
__format6 = BzrBranchFormat6()
1303
 
BranchFormat.register_format(__format5)
 
887
__default_format = BzrBranchFormat5()
 
888
BranchFormat.register_format(__default_format)
1304
889
BranchFormat.register_format(BranchReferenceFormat())
1305
 
BranchFormat.register_format(__format6)
1306
 
BranchFormat.set_default_format(__format6)
 
890
BranchFormat.set_default_format(__default_format)
1307
891
_legacy_formats = [BzrBranchFormat4(),
1308
892
                   ]
1309
893
 
1313
897
    Note that it's "local" in the context of the filesystem; it doesn't
1314
898
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1315
899
    it's writable, and can be accessed via the normal filesystem API.
1316
 
 
1317
 
    :ivar _transport: Transport for file operations on this branch's 
1318
 
        control files, typically pointing to the .bzr/branch directory.
1319
 
    :ivar repository: Repository for this branch.
1320
 
    :ivar base: The url of the base directory for this branch; the one 
1321
 
        containing the .bzr directory.
1322
900
    """
1323
901
    
1324
 
    def __init__(self, _format=None,
 
902
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
 
903
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
1325
904
                 _control_files=None, a_bzrdir=None, _repository=None):
1326
 
        """Create new branch object at a particular location."""
1327
 
        Branch.__init__(self)
 
905
        """Create new branch object at a particular location.
 
906
 
 
907
        transport -- A Transport object, defining how to access files.
 
908
        
 
909
        init -- If True, create new control files in a previously
 
910
             unversioned directory.  If False, the branch must already
 
911
             be versioned.
 
912
 
 
913
        relax_version_check -- If true, the usual check for the branch
 
914
            version is not applied.  This is intended only for
 
915
            upgrade/recovery type use; it's not guaranteed that
 
916
            all operations will work on old format branches.
 
917
        """
1328
918
        if a_bzrdir is None:
1329
 
            raise ValueError('a_bzrdir must be supplied')
 
919
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
1330
920
        else:
1331
921
            self.bzrdir = a_bzrdir
1332
 
        self._base = self.bzrdir.transport.clone('..').base
1333
 
        # XXX: We should be able to just do
1334
 
        #   self.base = self.bzrdir.root_transport.base
1335
 
        # but this does not quite work yet -- mbp 20080522
 
922
        self._transport = self.bzrdir.transport.clone('..')
 
923
        self._base = self._transport.base
1336
924
        self._format = _format
1337
925
        if _control_files is None:
1338
926
            raise ValueError('BzrBranch _control_files is None')
1339
927
        self.control_files = _control_files
1340
 
        self._transport = _control_files._transport
 
928
        if deprecated_passed(init):
 
929
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
930
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
931
                 DeprecationWarning,
 
932
                 stacklevel=2)
 
933
            if init:
 
934
                # this is slower than before deprecation, oh well never mind.
 
935
                # -> its deprecated.
 
936
                self._initialize(transport.base)
 
937
        self._check_format(_format)
 
938
        if deprecated_passed(relax_version_check):
 
939
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
940
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
941
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
 
942
                 "open() method.",
 
943
                 DeprecationWarning,
 
944
                 stacklevel=2)
 
945
            if (not relax_version_check
 
946
                and not self._format.is_supported()):
 
947
                raise errors.UnsupportedFormatError(format=fmt)
 
948
        if deprecated_passed(transport):
 
949
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
950
                 "parameter is deprecated as of bzr 0.8. "
 
951
                 "Please use Branch.open, or bzrdir.open_branch().",
 
952
                 DeprecationWarning,
 
953
                 stacklevel=2)
1341
954
        self.repository = _repository
1342
955
 
1343
956
    def __str__(self):
1345
958
 
1346
959
    __repr__ = __str__
1347
960
 
 
961
    def __del__(self):
 
962
        # TODO: It might be best to do this somewhere else,
 
963
        # but it is nice for a Branch object to automatically
 
964
        # cache it's information.
 
965
        # Alternatively, we could have the Transport objects cache requests
 
966
        # See the earlier discussion about how major objects (like Branch)
 
967
        # should never expect their __del__ function to run.
 
968
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
 
969
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
970
            try:
 
971
                osutils.rmtree(self.cache_root)
 
972
            except:
 
973
                pass
 
974
            self.cache_root = None
 
975
 
1348
976
    def _get_base(self):
1349
 
        """Returns the directory containing the control directory."""
1350
977
        return self._base
1351
978
 
1352
979
    base = property(_get_base, doc="The URL for the root of this branch.")
1353
980
 
1354
 
    @deprecated_method(deprecated_in((0, 16, 0)))
 
981
    def _finish_transaction(self):
 
982
        """Exit the current transaction."""
 
983
        return self.control_files._finish_transaction()
 
984
 
 
985
    def get_transaction(self):
 
986
        """Return the current active transaction.
 
987
 
 
988
        If no transaction is active, this returns a passthrough object
 
989
        for which all data is immediately flushed and no caching happens.
 
990
        """
 
991
        # this is an explicit function so that we can do tricky stuff
 
992
        # when the storage in rev_storage is elsewhere.
 
993
        # we probably need to hook the two 'lock a location' and 
 
994
        # 'have a transaction' together more delicately, so that
 
995
        # we can have two locks (branch and storage) and one transaction
 
996
        # ... and finishing the transaction unlocks both, but unlocking
 
997
        # does not. - RBC 20051121
 
998
        return self.control_files.get_transaction()
 
999
 
 
1000
    def _set_transaction(self, transaction):
 
1001
        """Set a new active transaction."""
 
1002
        return self.control_files._set_transaction(transaction)
 
1003
 
1355
1004
    def abspath(self, name):
1356
1005
        """See Branch.abspath."""
1357
 
        return self._transport.abspath(name)
 
1006
        return self.control_files._transport.abspath(name)
 
1007
 
 
1008
    def _check_format(self, format):
 
1009
        """Identify the branch format if needed.
 
1010
 
 
1011
        The format is stored as a reference to the format object in
 
1012
        self._format for code that needs to check it later.
 
1013
 
 
1014
        The format parameter is either None or the branch format class
 
1015
        used to open this branch.
 
1016
 
 
1017
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
 
1018
        """
 
1019
        if format is None:
 
1020
            format = BranchFormat.find_format(self.bzrdir)
 
1021
        self._format = format
 
1022
        mutter("got branch format %s", self._format)
 
1023
 
 
1024
    @needs_read_lock
 
1025
    def get_root_id(self):
 
1026
        """See Branch.get_root_id."""
 
1027
        tree = self.repository.revision_tree(self.last_revision())
 
1028
        return tree.inventory.root.file_id
1358
1029
 
1359
1030
    def is_locked(self):
1360
1031
        return self.control_files.is_locked()
1361
1032
 
1362
 
    def lock_write(self, token=None):
1363
 
        repo_token = self.repository.lock_write()
 
1033
    def lock_write(self):
 
1034
        self.repository.lock_write()
1364
1035
        try:
1365
 
            token = self.control_files.lock_write(token=token)
 
1036
            self.control_files.lock_write()
1366
1037
        except:
1367
1038
            self.repository.unlock()
1368
1039
            raise
1369
 
        return token
1370
1040
 
1371
1041
    def lock_read(self):
1372
1042
        self.repository.lock_read()
1382
1052
            self.control_files.unlock()
1383
1053
        finally:
1384
1054
            self.repository.unlock()
1385
 
        if not self.control_files.is_locked():
1386
 
            # we just released the lock
1387
 
            self._clear_cached_state()
1388
1055
        
1389
1056
    def peek_lock_mode(self):
1390
1057
        if self.control_files._lock_count == 0:
1400
1067
        """See Branch.print_file."""
1401
1068
        return self.repository.print_file(file, revision_id)
1402
1069
 
1403
 
    def _write_revision_history(self, history):
1404
 
        """Factored out of set_revision_history.
1405
 
 
1406
 
        This performs the actual writing to disk.
1407
 
        It is intended to be called by BzrBranch5.set_revision_history."""
1408
 
        self._transport.put_bytes(
1409
 
            'revision-history', '\n'.join(history),
1410
 
            mode=self.control_files._file_mode)
 
1070
    @needs_write_lock
 
1071
    def append_revision(self, *revision_ids):
 
1072
        """See Branch.append_revision."""
 
1073
        for revision_id in revision_ids:
 
1074
            mutter("add {%s} to revision-history" % revision_id)
 
1075
        rev_history = self.revision_history()
 
1076
        rev_history.extend(revision_ids)
 
1077
        self.set_revision_history(rev_history)
1411
1078
 
1412
1079
    @needs_write_lock
1413
1080
    def set_revision_history(self, rev_history):
1414
1081
        """See Branch.set_revision_history."""
1415
 
        if 'evil' in debug.debug_flags:
1416
 
            mutter_callsite(3, "set_revision_history scales with history.")
1417
 
        self._write_revision_history(rev_history)
1418
 
        self._clear_cached_state()
1419
 
        self._cache_revision_history(rev_history)
1420
 
        for hook in Branch.hooks['set_rh']:
1421
 
            hook(self, rev_history)
1422
 
 
1423
 
    def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1424
 
        """Run the post_change_branch_tip hooks."""
1425
 
        hooks = Branch.hooks['post_change_branch_tip']
1426
 
        if not hooks:
1427
 
            return
1428
 
        new_revno, new_revid = self.last_revision_info()
1429
 
        params = ChangeBranchTipParams(
1430
 
            self, old_revno, new_revno, old_revid, new_revid)
1431
 
        for hook in hooks:
1432
 
            hook(params)
1433
 
 
1434
 
    @needs_write_lock
1435
 
    def set_last_revision_info(self, revno, revision_id):
1436
 
        """Set the last revision of this branch.
1437
 
 
1438
 
        The caller is responsible for checking that the revno is correct
1439
 
        for this revision id.
1440
 
 
1441
 
        It may be possible to set the branch last revision to an id not
1442
 
        present in the repository.  However, branches can also be 
1443
 
        configured to check constraints on history, in which case this may not
1444
 
        be permitted.
1445
 
        """
1446
 
        revision_id = _mod_revision.ensure_null(revision_id)
1447
 
        old_revno, old_revid = self.last_revision_info()
1448
 
        # this old format stores the full history, but this api doesn't
1449
 
        # provide it, so we must generate, and might as well check it's
1450
 
        # correct
1451
 
        history = self._lefthand_history(revision_id)
1452
 
        if len(history) != revno:
1453
 
            raise AssertionError('%d != %d' % (len(history), revno))
1454
 
        self.set_revision_history(history)
1455
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1456
 
 
1457
 
    def _gen_revision_history(self):
1458
 
        history = self._transport.get_bytes('revision-history').split('\n')
1459
 
        if history[-1:] == ['']:
1460
 
            # There shouldn't be a trailing newline, but just in case.
1461
 
            history.pop()
1462
 
        return history
1463
 
 
1464
 
    def _lefthand_history(self, revision_id, last_rev=None,
1465
 
                          other_branch=None):
1466
 
        if 'evil' in debug.debug_flags:
1467
 
            mutter_callsite(4, "_lefthand_history scales with history.")
1468
 
        # stop_revision must be a descendant of last_revision
1469
 
        graph = self.repository.get_graph()
1470
 
        if last_rev is not None:
1471
 
            if not graph.is_ancestor(last_rev, revision_id):
1472
 
                # our previous tip is not merged into stop_revision
1473
 
                raise errors.DivergedBranches(self, other_branch)
1474
 
        # make a new revision history from the graph
1475
 
        parents_map = graph.get_parent_map([revision_id])
1476
 
        if revision_id not in parents_map:
1477
 
            raise errors.NoSuchRevision(self, revision_id)
1478
 
        current_rev_id = revision_id
1479
 
        new_history = []
1480
 
        # Do not include ghosts or graph origin in revision_history
1481
 
        while (current_rev_id in parents_map and
1482
 
               len(parents_map[current_rev_id]) > 0):
1483
 
            new_history.append(current_rev_id)
1484
 
            current_rev_id = parents_map[current_rev_id][0]
1485
 
            parents_map = graph.get_parent_map([current_rev_id])
1486
 
        new_history.reverse()
1487
 
        return new_history
1488
 
 
1489
 
    @needs_write_lock
1490
 
    def generate_revision_history(self, revision_id, last_rev=None,
 
1082
        self.control_files.put_utf8(
 
1083
            'revision-history', '\n'.join(rev_history))
 
1084
        transaction = self.get_transaction()
 
1085
        history = transaction.map.find_revision_history()
 
1086
        if history is not None:
 
1087
            # update the revision history in the identity map.
 
1088
            history[:] = list(rev_history)
 
1089
            # this call is disabled because revision_history is 
 
1090
            # not really an object yet, and the transaction is for objects.
 
1091
            # transaction.register_dirty(history)
 
1092
        else:
 
1093
            transaction.map.add_revision_history(rev_history)
 
1094
            # this call is disabled because revision_history is 
 
1095
            # not really an object yet, and the transaction is for objects.
 
1096
            # transaction.register_clean(history)
 
1097
 
 
1098
    @needs_read_lock
 
1099
    def revision_history(self):
 
1100
        """See Branch.revision_history."""
 
1101
        transaction = self.get_transaction()
 
1102
        history = transaction.map.find_revision_history()
 
1103
        if history is not None:
 
1104
            # mutter("cache hit for revision-history in %s", self)
 
1105
            return list(history)
 
1106
        decode_utf8 = cache_utf8.decode
 
1107
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1108
                self.control_files.get('revision-history').readlines()]
 
1109
        transaction.map.add_revision_history(history)
 
1110
        # this call is disabled because revision_history is 
 
1111
        # not really an object yet, and the transaction is for objects.
 
1112
        # transaction.register_clean(history, precious=True)
 
1113
        return list(history)
 
1114
 
 
1115
    @needs_write_lock
 
1116
    def generate_revision_history(self, revision_id, last_rev=None, 
1491
1117
        other_branch=None):
1492
1118
        """Create a new revision history that will finish with revision_id.
1493
 
 
 
1119
        
1494
1120
        :param revision_id: the new tip to use.
1495
1121
        :param last_rev: The previous last_revision. If not None, then this
1496
1122
            must be a ancestory of revision_id, or DivergedBranches is raised.
1497
1123
        :param other_branch: The other branch that DivergedBranches should
1498
1124
            raise with respect to.
1499
1125
        """
1500
 
        self.set_revision_history(self._lefthand_history(revision_id,
1501
 
            last_rev, other_branch))
 
1126
        # stop_revision must be a descendant of last_revision
 
1127
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1128
        if last_rev is not None and last_rev not in stop_graph:
 
1129
            # our previous tip is not merged into stop_revision
 
1130
            raise errors.DivergedBranches(self, other_branch)
 
1131
        # make a new revision history from the graph
 
1132
        current_rev_id = revision_id
 
1133
        new_history = []
 
1134
        while current_rev_id not in (None, revision.NULL_REVISION):
 
1135
            new_history.append(current_rev_id)
 
1136
            current_rev_id_parents = stop_graph[current_rev_id]
 
1137
            try:
 
1138
                current_rev_id = current_rev_id_parents[0]
 
1139
            except IndexError:
 
1140
                current_rev_id = None
 
1141
        new_history.reverse()
 
1142
        self.set_revision_history(new_history)
1502
1143
 
1503
1144
    @needs_write_lock
1504
 
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1145
    def update_revisions(self, other, stop_revision=None):
1505
1146
        """See Branch.update_revisions."""
1506
1147
        other.lock_read()
1507
1148
        try:
1508
 
            other_last_revno, other_last_revision = other.last_revision_info()
1509
1149
            if stop_revision is None:
1510
 
                stop_revision = other_last_revision
1511
 
                if _mod_revision.is_null(stop_revision):
 
1150
                stop_revision = other.last_revision()
 
1151
                if stop_revision is None:
1512
1152
                    # if there are no commits, we're done.
1513
1153
                    return
1514
1154
            # whats the current last revision, before we fetch [and change it
1515
1155
            # possibly]
1516
 
            last_rev = _mod_revision.ensure_null(self.last_revision())
1517
 
            # we fetch here so that we don't process data twice in the common
1518
 
            # case of having something to pull, and so that the check for 
1519
 
            # already merged can operate on the just fetched graph, which will
1520
 
            # be cached in memory.
 
1156
            last_rev = self.last_revision()
 
1157
            # we fetch here regardless of whether we need to so that we pickup
 
1158
            # filled in ghosts.
1521
1159
            self.fetch(other, stop_revision)
1522
 
            # Check to see if one is an ancestor of the other
1523
 
            if not overwrite:
1524
 
                heads = self.repository.get_graph().heads([stop_revision,
1525
 
                                                           last_rev])
1526
 
                if heads == set([last_rev]):
1527
 
                    # The current revision is a decendent of the target,
1528
 
                    # nothing to do
1529
 
                    return
1530
 
                elif heads == set([stop_revision, last_rev]):
1531
 
                    # These branches have diverged
1532
 
                    raise errors.DivergedBranches(self, other)
1533
 
                elif heads != set([stop_revision]):
1534
 
                    raise AssertionError("invalid heads: %r" % heads)
1535
 
            if other_last_revision == stop_revision:
1536
 
                self.set_last_revision_info(other_last_revno,
1537
 
                                            other_last_revision)
1538
 
            else:
1539
 
                # TODO: jam 2007-11-29 Is there a way to determine the
1540
 
                #       revno without searching all of history??
1541
 
                if overwrite:
1542
 
                    self.generate_revision_history(stop_revision)
1543
 
                else:
1544
 
                    self.generate_revision_history(stop_revision,
1545
 
                        last_rev=last_rev, other_branch=other)
 
1160
            my_ancestry = self.repository.get_ancestry(last_rev)
 
1161
            if stop_revision in my_ancestry:
 
1162
                # last_revision is a descendant of stop_revision
 
1163
                return
 
1164
            self.generate_revision_history(stop_revision, last_rev=last_rev,
 
1165
                other_branch=other)
1546
1166
        finally:
1547
1167
            other.unlock()
1548
1168
 
1550
1170
        """See Branch.basis_tree."""
1551
1171
        return self.repository.revision_tree(self.last_revision())
1552
1172
 
 
1173
    @deprecated_method(zero_eight)
 
1174
    def working_tree(self):
 
1175
        """Create a Working tree object for this branch."""
 
1176
 
 
1177
        from bzrlib.transport.local import LocalTransport
 
1178
        if (self.base.find('://') != -1 or 
 
1179
            not isinstance(self._transport, LocalTransport)):
 
1180
            raise NoWorkingTree(self.base)
 
1181
        return self.bzrdir.open_workingtree()
 
1182
 
1553
1183
    @needs_write_lock
1554
 
    def pull(self, source, overwrite=False, stop_revision=None,
1555
 
             _hook_master=None, run_hooks=True, possible_transports=None):
1556
 
        """See Branch.pull.
1557
 
 
1558
 
        :param _hook_master: Private parameter - set the branch to 
1559
 
            be supplied as the master to push hooks.
1560
 
        :param run_hooks: Private parameter - if false, this branch
1561
 
            is being called because it's the master of the primary branch,
1562
 
            so it should not run its hooks.
1563
 
        """
1564
 
        result = PullResult()
1565
 
        result.source_branch = source
1566
 
        result.target_branch = self
 
1184
    def pull(self, source, overwrite=False, stop_revision=None):
 
1185
        """See Branch.pull."""
1567
1186
        source.lock_read()
1568
1187
        try:
1569
 
            result.old_revno, result.old_revid = self.last_revision_info()
1570
 
            self.update_revisions(source, stop_revision, overwrite=overwrite)
1571
 
            result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1572
 
            result.new_revno, result.new_revid = self.last_revision_info()
1573
 
            if _hook_master:
1574
 
                result.master_branch = _hook_master
1575
 
                result.local_branch = self
1576
 
            else:
1577
 
                result.master_branch = self
1578
 
                result.local_branch = None
1579
 
            if run_hooks:
1580
 
                for hook in Branch.hooks['post_pull']:
1581
 
                    hook(result)
 
1188
            old_count = len(self.revision_history())
 
1189
            try:
 
1190
                self.update_revisions(source,stop_revision)
 
1191
            except DivergedBranches:
 
1192
                if not overwrite:
 
1193
                    raise
 
1194
            if overwrite:
 
1195
                self.set_revision_history(source.revision_history())
 
1196
            new_count = len(self.revision_history())
 
1197
            return new_count - old_count
1582
1198
        finally:
1583
1199
            source.unlock()
1584
 
        return result
1585
 
 
1586
 
    def _get_parent_location(self):
 
1200
 
 
1201
    def get_parent(self):
 
1202
        """See Branch.get_parent."""
 
1203
 
1587
1204
        _locs = ['parent', 'pull', 'x-pull']
 
1205
        assert self.base[-1] == '/'
1588
1206
        for l in _locs:
1589
1207
            try:
1590
 
                return self._transport.get_bytes(l).strip('\n')
1591
 
            except errors.NoSuchFile:
1592
 
                pass
 
1208
                parent = self.control_files.get(l).read().strip('\n')
 
1209
            except NoSuchFile:
 
1210
                continue
 
1211
            # This is an old-format absolute path to a local branch
 
1212
            # turn it into a url
 
1213
            if parent.startswith('/'):
 
1214
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1215
            try:
 
1216
                return urlutils.join(self.base[:-1], parent)
 
1217
            except errors.InvalidURLJoin, e:
 
1218
                raise errors.InaccessibleParent(parent, self.base)
1593
1219
        return None
1594
1220
 
1595
 
    @needs_read_lock
1596
 
    def push(self, target, overwrite=False, stop_revision=None,
1597
 
             _override_hook_source_branch=None):
1598
 
        """See Branch.push.
1599
 
 
1600
 
        This is the basic concrete implementation of push()
1601
 
 
1602
 
        :param _override_hook_source_branch: If specified, run
1603
 
        the hooks passing this Branch as the source, rather than self.  
1604
 
        This is for use of RemoteBranch, where push is delegated to the
1605
 
        underlying vfs-based Branch. 
1606
 
        """
1607
 
        # TODO: Public option to disable running hooks - should be trivial but
1608
 
        # needs tests.
1609
 
        target.lock_write()
1610
 
        try:
1611
 
            result = self._push_with_bound_branches(target, overwrite,
1612
 
                    stop_revision,
1613
 
                    _override_hook_source_branch=_override_hook_source_branch)
1614
 
            return result
1615
 
        finally:
1616
 
            target.unlock()
1617
 
 
1618
 
    def _push_with_bound_branches(self, target, overwrite,
1619
 
            stop_revision,
1620
 
            _override_hook_source_branch=None):
1621
 
        """Push from self into target, and into target's master if any.
1622
 
        
1623
 
        This is on the base BzrBranch class even though it doesn't support 
1624
 
        bound branches because the *target* might be bound.
1625
 
        """
1626
 
        def _run_hooks():
1627
 
            if _override_hook_source_branch:
1628
 
                result.source_branch = _override_hook_source_branch
1629
 
            for hook in Branch.hooks['post_push']:
1630
 
                hook(result)
1631
 
 
1632
 
        bound_location = target.get_bound_location()
1633
 
        if bound_location and target.base != bound_location:
1634
 
            # there is a master branch.
1635
 
            #
1636
 
            # XXX: Why the second check?  Is it even supported for a branch to
1637
 
            # be bound to itself? -- mbp 20070507
1638
 
            master_branch = target.get_master_branch()
1639
 
            master_branch.lock_write()
1640
 
            try:
1641
 
                # push into the master from this branch.
1642
 
                self._basic_push(master_branch, overwrite, stop_revision)
1643
 
                # and push into the target branch from this. Note that we push from
1644
 
                # this branch again, because its considered the highest bandwidth
1645
 
                # repository.
1646
 
                result = self._basic_push(target, overwrite, stop_revision)
1647
 
                result.master_branch = master_branch
1648
 
                result.local_branch = target
1649
 
                _run_hooks()
1650
 
                return result
1651
 
            finally:
1652
 
                master_branch.unlock()
1653
 
        else:
1654
 
            # no master branch
1655
 
            result = self._basic_push(target, overwrite, stop_revision)
1656
 
            # TODO: Why set master_branch and local_branch if there's no
1657
 
            # binding?  Maybe cleaner to just leave them unset? -- mbp
1658
 
            # 20070504
1659
 
            result.master_branch = target
1660
 
            result.local_branch = None
1661
 
            _run_hooks()
1662
 
            return result
1663
 
 
1664
 
    def _basic_push(self, target, overwrite, stop_revision):
1665
 
        """Basic implementation of push without bound branches or hooks.
1666
 
 
1667
 
        Must be called with self read locked and target write locked.
1668
 
        """
1669
 
        result = PushResult()
1670
 
        result.source_branch = self
1671
 
        result.target_branch = target
1672
 
        result.old_revno, result.old_revid = target.last_revision_info()
1673
 
        target.update_revisions(self, stop_revision, overwrite)
1674
 
        result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1675
 
        result.new_revno, result.new_revid = target.last_revision_info()
1676
 
        return result
1677
 
 
1678
 
    def get_parent(self):
1679
 
        """See Branch.get_parent."""
1680
 
        parent = self._get_parent_location()
1681
 
        if parent is None:
1682
 
            return parent
1683
 
        # This is an old-format absolute path to a local branch
1684
 
        # turn it into a url
1685
 
        if parent.startswith('/'):
1686
 
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
1687
 
        try:
1688
 
            return urlutils.join(self.base[:-1], parent)
1689
 
        except errors.InvalidURLJoin, e:
1690
 
            raise errors.InaccessibleParent(parent, self.base)
 
1221
    def get_push_location(self):
 
1222
        """See Branch.get_push_location."""
 
1223
        push_loc = self.get_config().get_user_option('push_location')
 
1224
        return push_loc
1691
1225
 
1692
1226
    def set_push_location(self, location):
1693
1227
        """See Branch.set_push_location."""
1694
 
        self.get_config().set_user_option(
1695
 
            'push_location', location,
1696
 
            store=_mod_config.STORE_LOCATION_NORECURSE)
 
1228
        self.get_config().set_user_option('push_location', location, 
 
1229
                                          local=True)
1697
1230
 
1698
1231
    @needs_write_lock
1699
1232
    def set_parent(self, url):
1701
1234
        # TODO: Maybe delete old location files?
1702
1235
        # URLs should never be unicode, even on the local fs,
1703
1236
        # FIXUP this and get_parent in a future branch format bump:
1704
 
        # read and rewrite the file. RBC 20060125
1705
 
        if url is not None:
 
1237
        # read and rewrite the file, and have the new format code read
 
1238
        # using .get not .get_utf8. RBC 20060125
 
1239
        if url is None:
 
1240
            self.control_files._transport.delete('parent')
 
1241
        else:
1706
1242
            if isinstance(url, unicode):
1707
 
                try:
 
1243
                try: 
1708
1244
                    url = url.encode('ascii')
1709
1245
                except UnicodeEncodeError:
1710
 
                    raise errors.InvalidURL(url,
 
1246
                    raise bzrlib.errors.InvalidURL(url,
1711
1247
                        "Urls must be 7-bit ascii, "
1712
1248
                        "use bzrlib.urlutils.escape")
 
1249
                    
1713
1250
            url = urlutils.relative_url(self.base, url)
1714
 
        self._set_parent_location(url)
 
1251
            self.control_files.put('parent', url + '\n')
1715
1252
 
1716
 
    def _set_parent_location(self, url):
1717
 
        if url is None:
1718
 
            self._transport.delete('parent')
1719
 
        else:
1720
 
            self._transport.put_bytes('parent', url + '\n',
1721
 
                mode=self.control_files._file_mode)
 
1253
    @deprecated_function(zero_nine)
 
1254
    def tree_config(self):
 
1255
        """DEPRECATED; call get_config instead.  
 
1256
        TreeConfig has become part of BranchConfig."""
 
1257
        return TreeConfig(self)
1722
1258
 
1723
1259
 
1724
1260
class BzrBranch5(BzrBranch):
1725
 
    """A format 5 branch. This supports new features over plain branches.
 
1261
    """A format 5 branch. This supports new features over plan branches.
1726
1262
 
1727
1263
    It has support for a master_branch which is the data for bound branches.
1728
1264
    """
1738
1274
                                         _repository=_repository)
1739
1275
        
1740
1276
    @needs_write_lock
1741
 
    def pull(self, source, overwrite=False, stop_revision=None,
1742
 
             run_hooks=True, possible_transports=None):
1743
 
        """Pull from source into self, updating my master if any.
1744
 
        
1745
 
        :param run_hooks: Private parameter - if false, this branch
1746
 
            is being called because it's the master of the primary branch,
1747
 
            so it should not run its hooks.
1748
 
        """
 
1277
    def pull(self, source, overwrite=False, stop_revision=None):
 
1278
        """Updates branch.pull to be bound branch aware."""
1749
1279
        bound_location = self.get_bound_location()
1750
 
        master_branch = None
1751
 
        if bound_location and source.base != bound_location:
 
1280
        if source.base != bound_location:
1752
1281
            # not pulling from master, so we need to update master.
1753
 
            master_branch = self.get_master_branch(possible_transports)
1754
 
            master_branch.lock_write()
1755
 
        try:
1756
 
            if master_branch:
1757
 
                # pull from source into master.
1758
 
                master_branch.pull(source, overwrite, stop_revision,
1759
 
                    run_hooks=False)
1760
 
            return super(BzrBranch5, self).pull(source, overwrite,
1761
 
                stop_revision, _hook_master=master_branch,
1762
 
                run_hooks=run_hooks)
1763
 
        finally:
1764
 
            if master_branch:
1765
 
                master_branch.unlock()
 
1282
            master_branch = self.get_master_branch()
 
1283
            if master_branch:
 
1284
                master_branch.pull(source)
 
1285
                source = master_branch
 
1286
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1766
1287
 
1767
1288
    def get_bound_location(self):
1768
1289
        try:
1769
 
            return self._transport.get_bytes('bound')[:-1]
 
1290
            return self.control_files.get_utf8('bound').read()[:-1]
1770
1291
        except errors.NoSuchFile:
1771
1292
            return None
1772
1293
 
1773
1294
    @needs_read_lock
1774
 
    def get_master_branch(self, possible_transports=None):
 
1295
    def get_master_branch(self):
1775
1296
        """Return the branch we are bound to.
1776
1297
        
1777
1298
        :return: Either a Branch, or None
1785
1306
        if not bound_loc:
1786
1307
            return None
1787
1308
        try:
1788
 
            return Branch.open(bound_loc,
1789
 
                               possible_transports=possible_transports)
 
1309
            return Branch.open(bound_loc)
1790
1310
        except (errors.NotBranchError, errors.ConnectionError), e:
1791
1311
            raise errors.BoundBranchConnectionFailure(
1792
1312
                    self, bound_loc, e)
1798
1318
        :param location: URL to the target branch
1799
1319
        """
1800
1320
        if location:
1801
 
            self._transport.put_bytes('bound', location+'\n',
1802
 
                mode=self.bzrdir._get_file_mode())
 
1321
            self.control_files.put_utf8('bound', location+'\n')
1803
1322
        else:
1804
1323
            try:
1805
 
                self._transport.delete('bound')
1806
 
            except errors.NoSuchFile:
 
1324
                self.control_files._transport.delete('bound')
 
1325
            except NoSuchFile:
1807
1326
                return False
1808
1327
            return True
1809
1328
 
1810
1329
    @needs_write_lock
1811
1330
    def bind(self, other):
1812
 
        """Bind this branch to the branch other.
 
1331
        """Bind the local branch the other branch.
1813
1332
 
1814
 
        This does not push or pull data between the branches, though it does
1815
 
        check for divergence to raise an error when the branches are not
1816
 
        either the same, or one a prefix of the other. That behaviour may not
1817
 
        be useful, so that check may be removed in future.
1818
 
        
1819
1333
        :param other: The branch to bind to
1820
1334
        :type other: Branch
1821
1335
        """
1826
1340
        #       but binding itself may not be.
1827
1341
        #       Since we *have* to check at commit time, we don't
1828
1342
        #       *need* to check here
1829
 
 
1830
 
        # we want to raise diverged if:
1831
 
        # last_rev is not in the other_last_rev history, AND
1832
 
        # other_last_rev is not in our history, and do it without pulling
1833
 
        # history around
 
1343
        self.pull(other)
 
1344
 
 
1345
        # we are now equal to or a suffix of other.
 
1346
 
 
1347
        # Since we have 'pulled' from the remote location,
 
1348
        # now we should try to pull in the opposite direction
 
1349
        # in case the local tree has more revisions than the
 
1350
        # remote one.
 
1351
        # There may be a different check you could do here
 
1352
        # rather than actually trying to install revisions remotely.
 
1353
        # TODO: capture an exception which indicates the remote branch
 
1354
        #       is not writable. 
 
1355
        #       If it is up-to-date, this probably should not be a failure
 
1356
        
 
1357
        # lock other for write so the revision-history syncing cannot race
 
1358
        other.lock_write()
 
1359
        try:
 
1360
            other.pull(self)
 
1361
            # if this does not error, other now has the same last rev we do
 
1362
            # it can only error if the pull from other was concurrent with
 
1363
            # a commit to other from someone else.
 
1364
 
 
1365
            # until we ditch revision-history, we need to sync them up:
 
1366
            self.set_revision_history(other.revision_history())
 
1367
            # now other and self are up to date with each other and have the
 
1368
            # same revision-history.
 
1369
        finally:
 
1370
            other.unlock()
 
1371
 
1834
1372
        self.set_bound_location(other.base)
1835
1373
 
1836
1374
    @needs_write_lock
1839
1377
        return self.set_bound_location(None)
1840
1378
 
1841
1379
    @needs_write_lock
1842
 
    def update(self, possible_transports=None):
 
1380
    def update(self):
1843
1381
        """Synchronise this branch with the master branch if any. 
1844
1382
 
1845
1383
        :return: None or the last_revision that was pivoted out during the
1846
1384
                 update.
1847
1385
        """
1848
 
        master = self.get_master_branch(possible_transports)
 
1386
        master = self.get_master_branch()
1849
1387
        if master is not None:
1850
 
            old_tip = _mod_revision.ensure_null(self.last_revision())
 
1388
            old_tip = self.last_revision()
1851
1389
            self.pull(master, overwrite=True)
1852
 
            if self.repository.get_graph().is_ancestor(old_tip,
1853
 
                _mod_revision.ensure_null(self.last_revision())):
 
1390
            if old_tip in self.repository.get_ancestry(self.last_revision()):
1854
1391
                return None
1855
1392
            return old_tip
1856
1393
        return None
1857
1394
 
1858
1395
 
1859
 
class BzrBranch6(BzrBranch5):
1860
 
 
1861
 
    def __init__(self, *args, **kwargs):
1862
 
        super(BzrBranch6, self).__init__(*args, **kwargs)
1863
 
        self._last_revision_info_cache = None
1864
 
        self._partial_revision_history_cache = []
1865
 
 
1866
 
    def _clear_cached_state(self):
1867
 
        super(BzrBranch6, self)._clear_cached_state()
1868
 
        self._last_revision_info_cache = None
1869
 
        self._partial_revision_history_cache = []
1870
 
 
1871
 
    @needs_read_lock
1872
 
    def last_revision_info(self):
1873
 
        """Return information about the last revision.
1874
 
 
1875
 
        :return: A tuple (revno, revision_id).
1876
 
        """
1877
 
        if self._last_revision_info_cache is None:
1878
 
            self._last_revision_info_cache = self._last_revision_info()
1879
 
        return self._last_revision_info_cache
1880
 
 
1881
 
    def _last_revision_info(self):
1882
 
        revision_string = self._transport.get_bytes('last-revision')
1883
 
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1884
 
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1885
 
        revno = int(revno)
1886
 
        return revno, revision_id
1887
 
 
1888
 
    def _write_last_revision_info(self, revno, revision_id):
1889
 
        """Simply write out the revision id, with no checks.
1890
 
 
1891
 
        Use set_last_revision_info to perform this safely.
1892
 
 
1893
 
        Does not update the revision_history cache.
1894
 
        Intended to be called by set_last_revision_info and
1895
 
        _write_revision_history.
1896
 
        """
1897
 
        revision_id = _mod_revision.ensure_null(revision_id)
1898
 
        out_string = '%d %s\n' % (revno, revision_id)
1899
 
        self._transport.put_bytes('last-revision', out_string,
1900
 
            mode=self.control_files._file_mode)
1901
 
 
1902
 
    @needs_write_lock
1903
 
    def set_last_revision_info(self, revno, revision_id):
1904
 
        revision_id = _mod_revision.ensure_null(revision_id)
1905
 
        old_revno, old_revid = self.last_revision_info()
1906
 
        if self._get_append_revisions_only():
1907
 
            self._check_history_violation(revision_id)
1908
 
        self._write_last_revision_info(revno, revision_id)
1909
 
        self._clear_cached_state()
1910
 
        self._last_revision_info_cache = revno, revision_id
1911
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1912
 
 
1913
 
    def _check_history_violation(self, revision_id):
1914
 
        last_revision = _mod_revision.ensure_null(self.last_revision())
1915
 
        if _mod_revision.is_null(last_revision):
1916
 
            return
1917
 
        if last_revision not in self._lefthand_history(revision_id):
1918
 
            raise errors.AppendRevisionsOnlyViolation(self.base)
1919
 
 
1920
 
    def _gen_revision_history(self):
1921
 
        """Generate the revision history from last revision
1922
 
        """
1923
 
        self._extend_partial_history()
1924
 
        return list(reversed(self._partial_revision_history_cache))
1925
 
 
1926
 
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
1927
 
        """Extend the partial history to include a given index
1928
 
 
1929
 
        If a stop_index is supplied, stop when that index has been reached.
1930
 
        If a stop_revision is supplied, stop when that revision is
1931
 
        encountered.  Otherwise, stop when the beginning of history is
1932
 
        reached.
1933
 
 
1934
 
        :param stop_index: The index which should be present.  When it is
1935
 
            present, history extension will stop.
1936
 
        :param revision_id: The revision id which should be present.  When
1937
 
            it is encountered, history extension will stop.
1938
 
        """
1939
 
        repo = self.repository
1940
 
        if len(self._partial_revision_history_cache) == 0:
1941
 
            iterator = repo.iter_reverse_revision_history(self.last_revision())
1942
 
        else:
1943
 
            start_revision = self._partial_revision_history_cache[-1]
1944
 
            iterator = repo.iter_reverse_revision_history(start_revision)
1945
 
            #skip the last revision in the list
1946
 
            next_revision = iterator.next()
1947
 
        for revision_id in iterator:
1948
 
            self._partial_revision_history_cache.append(revision_id)
1949
 
            if (stop_index is not None and
1950
 
                len(self._partial_revision_history_cache) > stop_index):
1951
 
                break
1952
 
            if revision_id == stop_revision:
1953
 
                break
1954
 
 
1955
 
    def _write_revision_history(self, history):
1956
 
        """Factored out of set_revision_history.
1957
 
 
1958
 
        This performs the actual writing to disk, with format-specific checks.
1959
 
        It is intended to be called by BzrBranch5.set_revision_history.
1960
 
        """
1961
 
        if len(history) == 0:
1962
 
            last_revision = 'null:'
1963
 
        else:
1964
 
            if history != self._lefthand_history(history[-1]):
1965
 
                raise errors.NotLefthandHistory(history)
1966
 
            last_revision = history[-1]
1967
 
        if self._get_append_revisions_only():
1968
 
            self._check_history_violation(last_revision)
1969
 
        self._write_last_revision_info(len(history), last_revision)
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', warn_masked=True)
1994
 
                return True
1995
 
        else:
1996
 
            self._set_config_location('bound_location', location,
1997
 
                                      config=config)
1998
 
            config.set_user_option('bound', 'True', warn_masked=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
 
            warn_masked=True)
2026
 
 
2027
 
    def _get_append_revisions_only(self):
2028
 
        value = self.get_config().get_user_option('append_revisions_only')
2029
 
        return value == 'True'
2030
 
 
2031
 
    def _synchronize_history(self, destination, revision_id):
2032
 
        """Synchronize last revision and revision history between branches.
2033
 
 
2034
 
        This version is most efficient when the destination is also a
2035
 
        BzrBranch6, but works for BzrBranch5, as long as the destination's
2036
 
        repository contains all the lefthand ancestors of the intended
2037
 
        last_revision.  If not, set_last_revision_info will fail.
2038
 
 
2039
 
        :param destination: The branch to copy the history into
2040
 
        :param revision_id: The revision-id to truncate history at.  May
2041
 
          be None to copy complete history.
2042
 
        """
2043
 
        source_revno, source_revision_id = self.last_revision_info()
2044
 
        if revision_id is None:
2045
 
            revno, revision_id = source_revno, source_revision_id
2046
 
        elif source_revision_id == revision_id:
2047
 
            # we know the revno without needing to walk all of history
2048
 
            revno = source_revno
2049
 
        else:
2050
 
            # To figure out the revno for a random revision, we need to build
2051
 
            # the revision history, and count its length.
2052
 
            # We don't care about the order, just how long it is.
2053
 
            # Alternatively, we could start at the current location, and count
2054
 
            # backwards. But there is no guarantee that we will find it since
2055
 
            # it may be a merged revision.
2056
 
            revno = len(list(self.repository.iter_reverse_revision_history(
2057
 
                                                                revision_id)))
2058
 
        destination.set_last_revision_info(revno, revision_id)
2059
 
 
2060
 
    def _make_tags(self):
2061
 
        return BasicTags(self)
2062
 
 
2063
 
    @needs_write_lock
2064
 
    def generate_revision_history(self, revision_id, last_rev=None,
2065
 
                                  other_branch=None):
2066
 
        """See BzrBranch5.generate_revision_history"""
2067
 
        history = self._lefthand_history(revision_id, last_rev, other_branch)
2068
 
        revno = len(history)
2069
 
        self.set_last_revision_info(revno, revision_id)
2070
 
 
2071
 
    @needs_read_lock
2072
 
    def get_rev_id(self, revno, history=None):
2073
 
        """Find the revision id of the specified revno."""
2074
 
        if revno == 0:
2075
 
            return _mod_revision.NULL_REVISION
2076
 
 
2077
 
        last_revno, last_revision_id = self.last_revision_info()
2078
 
        if revno <= 0 or revno > last_revno:
2079
 
            raise errors.NoSuchRevision(self, revno)
2080
 
 
2081
 
        if history is not None:
2082
 
            return history[revno - 1]
2083
 
 
2084
 
        index = last_revno - revno
2085
 
        if len(self._partial_revision_history_cache) <= index:
2086
 
            self._extend_partial_history(stop_index=index)
2087
 
        if len(self._partial_revision_history_cache) > index:
2088
 
            return self._partial_revision_history_cache[index]
2089
 
        else:
2090
 
            raise errors.NoSuchRevision(self, revno)
2091
 
 
2092
 
    @needs_read_lock
2093
 
    def revision_id_to_revno(self, revision_id):
2094
 
        """Given a revision id, return its revno"""
2095
 
        if _mod_revision.is_null(revision_id):
2096
 
            return 0
2097
 
        try:
2098
 
            index = self._partial_revision_history_cache.index(revision_id)
2099
 
        except ValueError:
2100
 
            self._extend_partial_history(stop_revision=revision_id)
2101
 
            index = len(self._partial_revision_history_cache) - 1
2102
 
            if self._partial_revision_history_cache[index] != revision_id:
2103
 
                raise errors.NoSuchRevision(self, revision_id)
2104
 
        return self.revno() - index
2105
 
 
2106
 
 
2107
 
######################################################################
2108
 
# results of operations
2109
 
 
2110
 
 
2111
 
class _Result(object):
2112
 
 
2113
 
    def _show_tag_conficts(self, to_file):
2114
 
        if not getattr(self, 'tag_conflicts', None):
2115
 
            return
2116
 
        to_file.write('Conflicting tags:\n')
2117
 
        for name, value1, value2 in self.tag_conflicts:
2118
 
            to_file.write('    %s\n' % (name, ))
2119
 
 
2120
 
 
2121
 
class PullResult(_Result):
2122
 
    """Result of a Branch.pull operation.
2123
 
 
2124
 
    :ivar old_revno: Revision number before pull.
2125
 
    :ivar new_revno: Revision number after pull.
2126
 
    :ivar old_revid: Tip revision id before pull.
2127
 
    :ivar new_revid: Tip revision id after pull.
2128
 
    :ivar source_branch: Source (local) branch object.
2129
 
    :ivar master_branch: Master branch of the target, or None.
2130
 
    :ivar target_branch: Target/destination branch object.
2131
 
    """
2132
 
 
2133
 
    def __int__(self):
2134
 
        # DEPRECATED: pull used to return the change in revno
2135
 
        return self.new_revno - self.old_revno
2136
 
 
2137
 
    def report(self, to_file):
2138
 
        if not is_quiet():
2139
 
            if self.old_revid == self.new_revid:
2140
 
                to_file.write('No revisions to pull.\n')
2141
 
            else:
2142
 
                to_file.write('Now on revision %d.\n' % self.new_revno)
2143
 
        self._show_tag_conficts(to_file)
2144
 
 
2145
 
 
2146
 
class PushResult(_Result):
2147
 
    """Result of a Branch.push operation.
2148
 
 
2149
 
    :ivar old_revno: Revision number before push.
2150
 
    :ivar new_revno: Revision number after push.
2151
 
    :ivar old_revid: Tip revision id before push.
2152
 
    :ivar new_revid: Tip revision id after push.
2153
 
    :ivar source_branch: Source branch object.
2154
 
    :ivar master_branch: Master branch of the target, or None.
2155
 
    :ivar target_branch: Target/destination branch object.
2156
 
    """
2157
 
 
2158
 
    def __int__(self):
2159
 
        # DEPRECATED: push used to return the change in revno
2160
 
        return self.new_revno - self.old_revno
2161
 
 
2162
 
    def report(self, to_file):
2163
 
        """Write a human-readable description of the result."""
2164
 
        if self.old_revid == self.new_revid:
2165
 
            to_file.write('No new revisions to push.\n')
2166
 
        else:
2167
 
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2168
 
        self._show_tag_conficts(to_file)
 
1396
class BranchTestProviderAdapter(object):
 
1397
    """A tool to generate a suite testing multiple branch formats at once.
 
1398
 
 
1399
    This is done by copying the test once for each transport and injecting
 
1400
    the transport_server, transport_readonly_server, and branch_format
 
1401
    classes into each copy. Each copy is also given a new id() to make it
 
1402
    easy to identify.
 
1403
    """
 
1404
 
 
1405
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1406
        self._transport_server = transport_server
 
1407
        self._transport_readonly_server = transport_readonly_server
 
1408
        self._formats = formats
 
1409
    
 
1410
    def adapt(self, test):
 
1411
        result = TestSuite()
 
1412
        for branch_format, bzrdir_format in self._formats:
 
1413
            new_test = deepcopy(test)
 
1414
            new_test.transport_server = self._transport_server
 
1415
            new_test.transport_readonly_server = self._transport_readonly_server
 
1416
            new_test.bzrdir_format = bzrdir_format
 
1417
            new_test.branch_format = branch_format
 
1418
            def make_new_test_id():
 
1419
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
1420
                return lambda: new_id
 
1421
            new_test.id = make_new_test_id()
 
1422
            result.addTest(new_test)
 
1423
        return result
2169
1424
 
2170
1425
 
2171
1426
class BranchCheckResult(object):
2188
1443
             self.branch._format)
2189
1444
 
2190
1445
 
2191
 
class Converter5to6(object):
2192
 
    """Perform an in-place upgrade of format 5 to format 6"""
2193
 
 
2194
 
    def convert(self, branch):
2195
 
        # Data for 5 and 6 can peacefully coexist.
2196
 
        format = BzrBranchFormat6()
2197
 
        new_branch = format.open(branch.bzrdir, _found=True)
2198
 
 
2199
 
        # Copy source data into target
2200
 
        new_branch._write_last_revision_info(*branch.last_revision_info())
2201
 
        new_branch.set_parent(branch.get_parent())
2202
 
        new_branch.set_bound_location(branch.get_bound_location())
2203
 
        new_branch.set_push_location(branch.get_push_location())
2204
 
 
2205
 
        # New branch has no tags by default
2206
 
        new_branch.tags._set_tag_dict({})
2207
 
 
2208
 
        # Copying done; now update target format
2209
 
        new_branch._transport.put_bytes('format',
2210
 
            format.get_format_string(),
2211
 
            mode=new_branch.control_files._file_mode)
2212
 
 
2213
 
        # Clean up old files
2214
 
        new_branch._transport.delete('revision-history')
2215
 
        try:
2216
 
            branch.set_parent(None)
2217
 
        except errors.NoSuchFile:
2218
 
            pass
2219
 
        branch.set_bound_location(None)
 
1446
######################################################################
 
1447
# predicates
 
1448
 
 
1449
 
 
1450
@deprecated_function(zero_eight)
 
1451
def is_control_file(*args, **kwargs):
 
1452
    """See bzrlib.workingtree.is_control_file."""
 
1453
    return bzrlib.workingtree.is_control_file(*args, **kwargs)