~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-05 15:52:12 UTC
  • mto: (2485.8.44 bzr.connection.sharing)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070605155212-k2za98dhobeikxhn
Fix pull multiple connections.

* bzrlib/builtins.py:
(cmd_pull.run): If 'location' wasn't a bundle, the transport may
be reused.

* bzrlib/branch.py:
(Branch.open_from_transport): New method.

* bzrlib/bundle/__init__.py:
(read_mergeable_from_transport): New method.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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 cStringIO import StringIO
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
 
21
lazy_import(globals(), """
18
22
from copy import deepcopy
19
 
from cStringIO import StringIO
20
23
from unittest import TestSuite
21
24
from warnings import warn
22
25
 
24
27
from bzrlib import (
25
28
        bzrdir,
26
29
        cache_utf8,
 
30
        config as _mod_config,
27
31
        errors,
28
32
        lockdir,
 
33
        lockable_files,
29
34
        osutils,
30
 
        revision,
 
35
        revision as _mod_revision,
31
36
        transport,
32
37
        tree,
 
38
        tsort,
33
39
        ui,
34
40
        urlutils,
35
41
        )
36
 
from bzrlib.config import TreeConfig
 
42
from bzrlib.config import BranchConfig, TreeConfig
 
43
from bzrlib.lockable_files import LockableFiles, TransportLock
 
44
from bzrlib.tag import (
 
45
    BasicTags,
 
46
    DisabledTags,
 
47
    )
 
48
""")
 
49
 
37
50
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
 
import bzrlib.errors as errors
39
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
40
 
                           HistoryMissing, InvalidRevisionId, 
41
 
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
51
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
 
52
                           HistoryMissing, InvalidRevisionId,
 
53
                           InvalidRevisionNumber, LockError, NoSuchFile,
42
54
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
43
 
                           NotBranchError, UninitializableFormat, 
44
 
                           UnlistableStore, UnlistableBranch, 
 
55
                           NotBranchError, UninitializableFormat,
 
56
                           UnlistableStore, UnlistableBranch,
45
57
                           )
46
 
from bzrlib.lockable_files import LockableFiles, TransportLock
 
58
from bzrlib.hooks import Hooks
47
59
from bzrlib.symbol_versioning import (deprecated_function,
48
60
                                      deprecated_method,
49
61
                                      DEPRECATED_PARAMETER,
50
62
                                      deprecated_passed,
51
 
                                      zero_eight, zero_nine,
 
63
                                      zero_eight, zero_nine, zero_sixteen,
52
64
                                      )
53
65
from bzrlib.trace import mutter, note
54
66
 
55
67
 
56
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
57
69
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
58
 
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
70
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
59
71
 
60
72
 
61
73
# TODO: Maybe include checks for common corruption of newlines, etc?
75
87
 
76
88
    base
77
89
        Base directory/url of the branch.
 
90
 
 
91
    hooks: An instance of BranchHooks.
78
92
    """
79
93
    # this is really an instance variable - FIXME move it there
80
94
    # - RBC 20060112
81
95
    base = None
82
96
 
 
97
    # override this to set the strategy for storing tags
 
98
    def _make_tags(self):
 
99
        return DisabledTags(self)
 
100
 
83
101
    def __init__(self, *ignored, **ignored_too):
84
 
        raise NotImplementedError('The Branch class is abstract')
 
102
        self.tags = self._make_tags()
 
103
        self._revision_history_cache = None
 
104
        self._revision_id_to_revno_cache = None
85
105
 
86
106
    def break_lock(self):
87
107
        """Break a lock if one is present from another instance.
102
122
    def open_downlevel(base):
103
123
        """Open a branch which may be of an old format."""
104
124
        return Branch.open(base, _unsupported=True)
105
 
        
 
125
 
106
126
    @staticmethod
107
127
    def open(base, _unsupported=False):
108
128
        """Open the branch rooted at base.
114
134
        return control.open_branch(_unsupported)
115
135
 
116
136
    @staticmethod
 
137
    def open_from_transport(transport, _unsupported=False):
 
138
        """Open the branch rooted at transport"""
 
139
        control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
 
140
        return control.open_branch(_unsupported)
 
141
 
 
142
    @staticmethod
117
143
    def open_containing(url):
118
144
        """Open an existing branch which contains url.
119
145
        
138
164
        """
139
165
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
140
166
 
 
167
    @deprecated_function(zero_eight)
141
168
    def setup_caching(self, cache_root):
142
169
        """Subclasses that care about caching should override this, and set
143
170
        up cached stores located under cache_root.
 
171
        
 
172
        NOTE: This is unused.
144
173
        """
145
 
        # seems to be unused, 2006-01-13 mbp
146
 
        warn('%s is deprecated' % self.setup_caching)
147
 
        self.cache_root = cache_root
 
174
        pass
148
175
 
149
176
    def get_config(self):
150
 
        return bzrlib.config.BranchConfig(self)
 
177
        return BranchConfig(self)
151
178
 
152
179
    def _get_nick(self):
153
180
        return self.get_config().get_nickname()
176
203
    def get_physical_lock_status(self):
177
204
        raise NotImplementedError(self.get_physical_lock_status)
178
205
 
 
206
    @needs_read_lock
 
207
    def get_revision_id_to_revno_map(self):
 
208
        """Return the revision_id => dotted revno map.
 
209
 
 
210
        This will be regenerated on demand, but will be cached.
 
211
 
 
212
        :return: A dictionary mapping revision_id => dotted revno.
 
213
            This dictionary should not be modified by the caller.
 
214
        """
 
215
        if self._revision_id_to_revno_cache is not None:
 
216
            mapping = self._revision_id_to_revno_cache
 
217
        else:
 
218
            mapping = self._gen_revno_map()
 
219
            self._cache_revision_id_to_revno(mapping)
 
220
        # TODO: jam 20070417 Since this is being cached, should we be returning
 
221
        #       a copy?
 
222
        # I would rather not, and instead just declare that users should not
 
223
        # modify the return value.
 
224
        return mapping
 
225
 
 
226
    def _gen_revno_map(self):
 
227
        """Create a new mapping from revision ids to dotted revnos.
 
228
 
 
229
        Dotted revnos are generated based on the current tip in the revision
 
230
        history.
 
231
        This is the worker function for get_revision_id_to_revno_map, which
 
232
        just caches the return value.
 
233
 
 
234
        :return: A dictionary mapping revision_id => dotted revno.
 
235
        """
 
236
        last_revision = self.last_revision()
 
237
        revision_graph = self.repository.get_revision_graph(last_revision)
 
238
        merge_sorted_revisions = tsort.merge_sort(
 
239
            revision_graph,
 
240
            last_revision,
 
241
            None,
 
242
            generate_revno=True)
 
243
        revision_id_to_revno = dict((rev_id, revno)
 
244
                                    for seq_num, rev_id, depth, revno, end_of_merge
 
245
                                     in merge_sorted_revisions)
 
246
        return revision_id_to_revno
 
247
 
 
248
    def leave_lock_in_place(self):
 
249
        """Tell this branch object not to release the physical lock when this
 
250
        object is unlocked.
 
251
        
 
252
        If lock_write doesn't return a token, then this method is not supported.
 
253
        """
 
254
        self.control_files.leave_in_place()
 
255
 
 
256
    def dont_leave_lock_in_place(self):
 
257
        """Tell this branch object to release the physical lock when this
 
258
        object is unlocked, even if it didn't originally acquire it.
 
259
 
 
260
        If lock_write doesn't return a token, then this method is not supported.
 
261
        """
 
262
        self.control_files.dont_leave_in_place()
 
263
 
179
264
    def abspath(self, name):
180
265
        """Return absolute filename for something in the branch
181
266
        
216
301
        try:
217
302
            if last_revision is None:
218
303
                pb.update('get source history')
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
 
304
                last_revision = from_branch.last_revision()
 
305
                if last_revision is None:
 
306
                    last_revision = _mod_revision.NULL_REVISION
225
307
            return self.repository.fetch(from_branch.repository,
226
308
                                         revision_id=last_revision,
227
309
                                         pb=nested_pb)
238
320
        """
239
321
        return None
240
322
    
 
323
    def get_old_bound_location(self):
 
324
        """Return the URL of the branch we used to be bound to
 
325
        """
 
326
        raise errors.UpgradeRequired(self.base)
 
327
 
241
328
    def get_commit_builder(self, parents, config=None, timestamp=None, 
242
329
                           timezone=None, committer=None, revprops=None, 
243
330
                           revision_id=None):
255
342
        if config is None:
256
343
            config = self.get_config()
257
344
        
258
 
        return self.repository.get_commit_builder(self, parents, config, 
 
345
        return self.repository.get_commit_builder(self, parents, config,
259
346
            timestamp, timezone, committer, revprops, revision_id)
260
347
 
261
348
    def get_master_branch(self):
277
364
            raise InvalidRevisionNumber(revno)
278
365
        return self.repository.get_revision_delta(rh[revno-1])
279
366
 
 
367
    @deprecated_method(zero_sixteen)
280
368
    def get_root_id(self):
281
 
        """Return the id of this branches root"""
 
369
        """Return the id of this branches root
 
370
 
 
371
        Deprecated: branches don't have root ids-- trees do.
 
372
        Use basis_tree().get_root_id() instead.
 
373
        """
282
374
        raise NotImplementedError(self.get_root_id)
283
375
 
284
376
    def print_file(self, file, revision_id):
291
383
    def set_revision_history(self, rev_history):
292
384
        raise NotImplementedError(self.set_revision_history)
293
385
 
 
386
    def _cache_revision_history(self, rev_history):
 
387
        """Set the cached revision history to rev_history.
 
388
 
 
389
        The revision_history method will use this cache to avoid regenerating
 
390
        the revision history.
 
391
 
 
392
        This API is semi-public; it only for use by subclasses, all other code
 
393
        should consider it to be private.
 
394
        """
 
395
        self._revision_history_cache = rev_history
 
396
 
 
397
    def _cache_revision_id_to_revno(self, revision_id_to_revno):
 
398
        """Set the cached revision_id => revno map to revision_id_to_revno.
 
399
 
 
400
        This API is semi-public; it only for use by subclasses, all other code
 
401
        should consider it to be private.
 
402
        """
 
403
        self._revision_id_to_revno_cache = revision_id_to_revno
 
404
 
 
405
    def _clear_cached_state(self):
 
406
        """Clear any cached data on this branch, e.g. cached revision history.
 
407
 
 
408
        This means the next call to revision_history will need to call
 
409
        _gen_revision_history.
 
410
 
 
411
        This API is semi-public; it only for use by subclasses, all other code
 
412
        should consider it to be private.
 
413
        """
 
414
        self._revision_history_cache = None
 
415
        self._revision_id_to_revno_cache = None
 
416
 
 
417
    def _gen_revision_history(self):
 
418
        """Return sequence of revision hashes on to this branch.
 
419
        
 
420
        Unlike revision_history, this method always regenerates or rereads the
 
421
        revision history, i.e. it does not cache the result, so repeated calls
 
422
        may be expensive.
 
423
 
 
424
        Concrete subclasses should override this instead of revision_history so
 
425
        that subclasses do not need to deal with caching logic.
 
426
        
 
427
        This API is semi-public; it only for use by subclasses, all other code
 
428
        should consider it to be private.
 
429
        """
 
430
        raise NotImplementedError(self._gen_revision_history)
 
431
 
 
432
    @needs_read_lock
294
433
    def revision_history(self):
295
 
        """Return sequence of revision hashes on to this branch."""
296
 
        raise NotImplementedError('revision_history is abstract')
 
434
        """Return sequence of revision hashes on to this branch.
 
435
        
 
436
        This method will cache the revision history for as long as it is safe to
 
437
        do so.
 
438
        """
 
439
        if self._revision_history_cache is not None:
 
440
            history = self._revision_history_cache
 
441
        else:
 
442
            history = self._gen_revision_history()
 
443
            self._cache_revision_history(history)
 
444
        return list(history)
297
445
 
298
446
    def revno(self):
299
447
        """Return current revision number for this branch.
307
455
        """Older format branches cannot bind or unbind."""
308
456
        raise errors.UpgradeRequired(self.base)
309
457
 
 
458
    def set_append_revisions_only(self, enabled):
 
459
        """Older format branches are never restricted to append-only"""
 
460
        raise errors.UpgradeRequired(self.base)
 
461
 
310
462
    def last_revision(self):
311
463
        """Return last revision id, or None"""
312
464
        ph = self.revision_history()
315
467
        else:
316
468
            return None
317
469
 
 
470
    def last_revision_info(self):
 
471
        """Return information about the last revision.
 
472
 
 
473
        :return: A tuple (revno, last_revision_id).
 
474
        """
 
475
        rh = self.revision_history()
 
476
        revno = len(rh)
 
477
        if revno:
 
478
            return (revno, rh[-1])
 
479
        else:
 
480
            return (0, _mod_revision.NULL_REVISION)
 
481
 
318
482
    def missing_revisions(self, other, stop_revision=None):
319
483
        """Return a list of new revisions that would perfectly fit.
320
484
        
351
515
        """Given a revision id, return its revno"""
352
516
        if revision_id is None:
353
517
            return 0
 
518
        revision_id = osutils.safe_revision_id(revision_id)
354
519
        history = self.revision_history()
355
520
        try:
356
521
            return history.index(revision_id) + 1
357
522
        except ValueError:
358
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
523
            raise errors.NoSuchRevision(self, revision_id)
359
524
 
360
525
    def get_rev_id(self, revno, history=None):
361
526
        """Find the revision id of the specified revno."""
363
528
            return None
364
529
        if history is None:
365
530
            history = self.revision_history()
366
 
        elif revno <= 0 or revno > len(history):
367
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
531
        if revno <= 0 or revno > len(history):
 
532
            raise errors.NoSuchRevision(self, revno)
368
533
        return history[revno - 1]
369
534
 
370
535
    def pull(self, source, overwrite=False, stop_revision=None):
 
536
        """Mirror source into this branch.
 
537
 
 
538
        This branch is considered to be 'local', having low latency.
 
539
 
 
540
        :returns: PullResult instance
 
541
        """
371
542
        raise NotImplementedError(self.pull)
372
543
 
 
544
    def push(self, target, overwrite=False, stop_revision=None):
 
545
        """Mirror this branch into target.
 
546
 
 
547
        This branch is considered to be 'local', having low latency.
 
548
        """
 
549
        raise NotImplementedError(self.push)
 
550
 
373
551
    def basis_tree(self):
374
552
        """Return `Tree` object for last revision."""
375
553
        return self.repository.revision_tree(self.last_revision())
406
584
        """
407
585
        raise NotImplementedError(self.get_parent)
408
586
 
 
587
    def _set_config_location(self, name, url, config=None,
 
588
                             make_relative=False):
 
589
        if config is None:
 
590
            config = self.get_config()
 
591
        if url is None:
 
592
            url = ''
 
593
        elif make_relative:
 
594
            url = urlutils.relative_url(self.base, url)
 
595
        config.set_user_option(name, url)
 
596
 
 
597
    def _get_config_location(self, name, config=None):
 
598
        if config is None:
 
599
            config = self.get_config()
 
600
        location = config.get_user_option(name)
 
601
        if location == '':
 
602
            location = None
 
603
        return location
 
604
 
409
605
    def get_submit_branch(self):
410
606
        """Return the submit location of the branch.
411
607
 
424
620
        """
425
621
        self.get_config().set_user_option('submit_branch', location)
426
622
 
 
623
    def get_public_branch(self):
 
624
        """Return the public location of the branch.
 
625
 
 
626
        This is is used by merge directives.
 
627
        """
 
628
        return self._get_config_location('public_branch')
 
629
 
 
630
    def set_public_branch(self, location):
 
631
        """Return the submit location of the branch.
 
632
 
 
633
        This is the default location for bundle.  The usual
 
634
        pattern is that the user can override it by specifying a
 
635
        location.
 
636
        """
 
637
        self._set_config_location('public_branch', location)
 
638
 
427
639
    def get_push_location(self):
428
640
        """Return the None or the location to push this branch to."""
429
 
        raise NotImplementedError(self.get_push_location)
 
641
        push_loc = self.get_config().get_user_option('push_location')
 
642
        return push_loc
430
643
 
431
644
    def set_push_location(self, location):
432
645
        """Set a new push location for this branch."""
460
673
            raise InvalidRevisionNumber(revno)
461
674
 
462
675
    @needs_read_lock
463
 
    def clone(self, *args, **kwargs):
 
676
    def clone(self, to_bzrdir, revision_id=None):
464
677
        """Clone this branch into to_bzrdir preserving all semantic values.
465
678
        
466
679
        revision_id: if not None, the revision history in the new branch will
467
680
                     be truncated to end with revision_id.
468
681
        """
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')
514
682
        result = self._format.initialize(to_bzrdir)
515
683
        self.copy_content_into(result, revision_id=revision_id)
516
684
        return  result
527
695
        result.set_parent(self.bzrdir.root_transport.base)
528
696
        return result
529
697
 
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.
 
698
    def _synchronize_history(self, destination, revision_id):
 
699
        """Synchronize last revision and revision history between branches.
 
700
 
 
701
        This version is most efficient when the destination is also a
 
702
        BzrBranch5, but works for BzrBranch6 as long as the revision
 
703
        history is the true lefthand parent history, and all of the revisions
 
704
        are in the destination's repository.  If not, set_revision_history
 
705
        will fail.
 
706
 
 
707
        :param destination: The branch to copy the history into
 
708
        :param revision_id: The revision-id to truncate history at.  May
 
709
          be None to copy complete history.
536
710
        """
537
711
        new_history = self.revision_history()
538
712
        if revision_id is not None:
 
713
            revision_id = osutils.safe_revision_id(revision_id)
539
714
            try:
540
715
                new_history = new_history[:new_history.index(revision_id) + 1]
541
716
            except ValueError:
542
717
                rev = self.repository.get_revision(revision_id)
543
718
                new_history = rev.get_history(self.repository)[1:]
544
719
        destination.set_revision_history(new_history)
 
720
 
 
721
    @needs_read_lock
 
722
    def copy_content_into(self, destination, revision_id=None):
 
723
        """Copy the content of self into destination.
 
724
 
 
725
        revision_id: if not None, the revision history in the new branch will
 
726
                     be truncated to end with revision_id.
 
727
        """
 
728
        self._synchronize_history(destination, revision_id)
545
729
        try:
546
730
            parent = self.get_parent()
547
731
        except errors.InaccessibleParent, e:
549
733
        else:
550
734
            if parent:
551
735
                destination.set_parent(parent)
 
736
        self.tags.merge_to(destination.tags)
552
737
 
553
738
    @needs_read_lock
554
739
    def check(self):
580
765
            mainline_parent_id = revision_id
581
766
        return BranchCheckResult(self)
582
767
 
583
 
    def create_checkout(self, to_location, revision_id=None, 
 
768
    def _get_checkout_format(self):
 
769
        """Return the most suitable metadir for a checkout of this branch.
 
770
        Weaves are used if this branch's repository uses weaves.
 
771
        """
 
772
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
 
773
            from bzrlib.repofmt import weaverepo
 
774
            format = bzrdir.BzrDirMetaFormat1()
 
775
            format.repository_format = weaverepo.RepositoryFormat7()
 
776
        else:
 
777
            format = self.repository.bzrdir.checkout_metadir()
 
778
            format.set_branch_format(self._format)
 
779
        return format
 
780
 
 
781
    def create_checkout(self, to_location, revision_id=None,
584
782
                        lightweight=False):
585
783
        """Create a checkout of a branch.
586
784
        
590
788
        produce a bound branch (heavyweight checkout)
591
789
        :return: The tree of the created checkout
592
790
        """
 
791
        t = transport.get_transport(to_location)
 
792
        t.ensure_base()
593
793
        if lightweight:
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)
 
794
            format = self._get_checkout_format()
 
795
            checkout = format.initialize_on_transport(t)
600
796
            BranchReferenceFormat().initialize(checkout, self)
601
797
        else:
 
798
            format = self._get_checkout_format()
602
799
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
603
 
                to_location, force_new_tree=False)
 
800
                to_location, force_new_tree=False, format=format)
604
801
            checkout = checkout_branch.bzrdir
605
802
            checkout_branch.bind(self)
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)
 
803
            # pull up to the specified revision_id to set the initial 
 
804
            # branch tip correctly, and seed it with history.
 
805
            checkout_branch.pull(self, stop_revision=revision_id)
 
806
        tree = checkout.create_workingtree(revision_id)
 
807
        basis_tree = tree.basis_tree()
 
808
        basis_tree.lock_read()
 
809
        try:
 
810
            for path, file_id in basis_tree.iter_references():
 
811
                reference_parent = self.reference_parent(file_id, path)
 
812
                reference_parent.create_checkout(tree.abspath(path),
 
813
                    basis_tree.get_reference_revision(file_id, path),
 
814
                    lightweight)
 
815
        finally:
 
816
            basis_tree.unlock()
 
817
        return tree
 
818
 
 
819
    def reference_parent(self, file_id, path):
 
820
        """Return the parent branch for a tree-reference file_id
 
821
        :param file_id: The file_id of the tree reference
 
822
        :param path: The path of the file_id in the tree
 
823
        :return: A branch associated with the file_id
 
824
        """
 
825
        # FIXME should provide multiple branches, based on config
 
826
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
 
827
 
 
828
    def supports_tags(self):
 
829
        return self._format.supports_tags()
611
830
 
612
831
 
613
832
class BranchFormat(object):
651
870
        """Return the current default format."""
652
871
        return klass._default_format
653
872
 
 
873
    def get_reference(self, a_bzrdir):
 
874
        """Get the target reference of the branch in a_bzrdir.
 
875
 
 
876
        format probing must have been completed before calling
 
877
        this method - it is assumed that the format of the branch
 
878
        in a_bzrdir is correct.
 
879
 
 
880
        :param a_bzrdir: The bzrdir to get the branch data from.
 
881
        :return: None if the branch is not a reference branch.
 
882
        """
 
883
        return None
 
884
 
654
885
    def get_format_string(self):
655
886
        """Return the ASCII format string that identifies this format."""
656
887
        raise NotImplementedError(self.get_format_string)
657
888
 
658
889
    def get_format_description(self):
659
890
        """Return the short format description for this format."""
660
 
        raise NotImplementedError(self.get_format_string)
 
891
        raise NotImplementedError(self.get_format_description)
 
892
 
 
893
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
 
894
                           set_format=True):
 
895
        """Initialize a branch in a bzrdir, with specified files
 
896
 
 
897
        :param a_bzrdir: The bzrdir to initialize the branch in
 
898
        :param utf8_files: The files to create as a list of
 
899
            (filename, content) tuples
 
900
        :param set_format: If True, set the format with
 
901
            self.get_format_string.  (BzrBranch4 has its format set
 
902
            elsewhere)
 
903
        :return: a branch in this format
 
904
        """
 
905
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
906
        branch_transport = a_bzrdir.get_branch_transport(self)
 
907
        lock_map = {
 
908
            'metadir': ('lock', lockdir.LockDir),
 
909
            'branch4': ('branch-lock', lockable_files.TransportLock),
 
910
        }
 
911
        lock_name, lock_class = lock_map[lock_type]
 
912
        control_files = lockable_files.LockableFiles(branch_transport,
 
913
            lock_name, lock_class)
 
914
        control_files.create_lock()
 
915
        control_files.lock_write()
 
916
        if set_format:
 
917
            control_files.put_utf8('format', self.get_format_string())
 
918
        try:
 
919
            for file, content in utf8_files:
 
920
                control_files.put_utf8(file, content)
 
921
        finally:
 
922
            control_files.unlock()
 
923
        return self.open(a_bzrdir, _found=True)
661
924
 
662
925
    def initialize(self, a_bzrdir):
663
926
        """Create a branch of this format in a_bzrdir."""
696
959
    def __str__(self):
697
960
        return self.get_format_string().rstrip()
698
961
 
 
962
    def supports_tags(self):
 
963
        """True if this format supports tags stored in the branch"""
 
964
        return False  # by default
 
965
 
 
966
    # XXX: Probably doesn't really belong here -- mbp 20070212
 
967
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
 
968
            lock_class):
 
969
        branch_transport = a_bzrdir.get_branch_transport(self)
 
970
        control_files = lockable_files.LockableFiles(branch_transport,
 
971
            lock_filename, lock_class)
 
972
        control_files.create_lock()
 
973
        control_files.lock_write()
 
974
        try:
 
975
            for filename, content in utf8_files:
 
976
                control_files.put_utf8(filename, content)
 
977
        finally:
 
978
            control_files.unlock()
 
979
 
 
980
 
 
981
class BranchHooks(Hooks):
 
982
    """A dictionary mapping hook name to a list of callables for branch hooks.
 
983
    
 
984
    e.g. ['set_rh'] Is the list of items to be called when the
 
985
    set_revision_history function is invoked.
 
986
    """
 
987
 
 
988
    def __init__(self):
 
989
        """Create the default hooks.
 
990
 
 
991
        These are all empty initially, because by default nothing should get
 
992
        notified.
 
993
        """
 
994
        Hooks.__init__(self)
 
995
        # Introduced in 0.15:
 
996
        # invoked whenever the revision history has been set
 
997
        # with set_revision_history. The api signature is
 
998
        # (branch, revision_history), and the branch will
 
999
        # be write-locked.
 
1000
        self['set_rh'] = []
 
1001
        # invoked after a push operation completes.
 
1002
        # the api signature is
 
1003
        # (push_result)
 
1004
        # containing the members
 
1005
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
 
1006
        # where local is the local target branch or None, master is the target 
 
1007
        # master branch, and the rest should be self explanatory. The source
 
1008
        # is read locked and the target branches write locked. Source will
 
1009
        # be the local low-latency branch.
 
1010
        self['post_push'] = []
 
1011
        # invoked after a pull operation completes.
 
1012
        # the api signature is
 
1013
        # (pull_result)
 
1014
        # containing the members
 
1015
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
 
1016
        # where local is the local branch or None, master is the target 
 
1017
        # master branch, and the rest should be self explanatory. The source
 
1018
        # is read locked and the target branches write locked. The local
 
1019
        # branch is the low-latency branch.
 
1020
        self['post_pull'] = []
 
1021
        # invoked after a commit operation completes.
 
1022
        # the api signature is 
 
1023
        # (local, master, old_revno, old_revid, new_revno, new_revid)
 
1024
        # old_revid is NULL_REVISION for the first commit to a branch.
 
1025
        self['post_commit'] = []
 
1026
        # invoked after a uncommit operation completes.
 
1027
        # the api signature is
 
1028
        # (local, master, old_revno, old_revid, new_revno, new_revid) where
 
1029
        # local is the local branch or None, master is the target branch,
 
1030
        # and an empty branch recieves new_revno of 0, new_revid of None.
 
1031
        self['post_uncommit'] = []
 
1032
 
 
1033
 
 
1034
# install the default hooks into the Branch class.
 
1035
Branch.hooks = BranchHooks()
 
1036
 
699
1037
 
700
1038
class BzrBranchFormat4(BranchFormat):
701
1039
    """Bzr branch format 4.
711
1049
 
712
1050
    def initialize(self, a_bzrdir):
713
1051
        """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)
716
1052
        utf8_files = [('revision-history', ''),
717
1053
                      ('branch-name', ''),
718
1054
                      ]
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)
 
1055
        return self._initialize_helper(a_bzrdir, utf8_files,
 
1056
                                       lock_type='branch4', set_format=False)
729
1057
 
730
1058
    def __init__(self):
731
1059
        super(BzrBranchFormat4, self).__init__()
772
1100
        
773
1101
    def initialize(self, a_bzrdir):
774
1102
        """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)
777
1103
        utf8_files = [('revision-history', ''),
778
1104
                      ('branch-name', ''),
779
1105
                      ]
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, )
 
1106
        return self._initialize_helper(a_bzrdir, utf8_files)
790
1107
 
791
1108
    def __init__(self):
792
1109
        super(BzrBranchFormat5, self).__init__()
801
1118
        if not _found:
802
1119
            format = BranchFormat.find_format(a_bzrdir)
803
1120
            assert format.__class__ == self.__class__
 
1121
        try:
 
1122
            transport = a_bzrdir.get_branch_transport(None)
 
1123
            control_files = lockable_files.LockableFiles(transport, 'lock',
 
1124
                                                         lockdir.LockDir)
 
1125
            return BzrBranch5(_format=self,
 
1126
                              _control_files=control_files,
 
1127
                              a_bzrdir=a_bzrdir,
 
1128
                              _repository=a_bzrdir.find_repository())
 
1129
        except NoSuchFile:
 
1130
            raise NotBranchError(path=transport.base)
 
1131
 
 
1132
 
 
1133
class BzrBranchFormat6(BzrBranchFormat5):
 
1134
    """Branch format with last-revision
 
1135
 
 
1136
    Unlike previous formats, this has no explicit revision history. Instead,
 
1137
    this just stores the last-revision, and the left-hand history leading
 
1138
    up to there is the history.
 
1139
 
 
1140
    This format was introduced in bzr 0.15
 
1141
    """
 
1142
 
 
1143
    def get_format_string(self):
 
1144
        """See BranchFormat.get_format_string()."""
 
1145
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
 
1146
 
 
1147
    def get_format_description(self):
 
1148
        """See BranchFormat.get_format_description()."""
 
1149
        return "Branch format 6"
 
1150
 
 
1151
    def initialize(self, a_bzrdir):
 
1152
        """Create a branch of this format in a_bzrdir."""
 
1153
        utf8_files = [('last-revision', '0 null:\n'),
 
1154
                      ('branch-name', ''),
 
1155
                      ('branch.conf', ''),
 
1156
                      ('tags', ''),
 
1157
                      ]
 
1158
        return self._initialize_helper(a_bzrdir, utf8_files)
 
1159
 
 
1160
    def open(self, a_bzrdir, _found=False):
 
1161
        """Return the branch object for a_bzrdir
 
1162
 
 
1163
        _found is a private parameter, do not use it. It is used to indicate
 
1164
               if format probing has already be done.
 
1165
        """
 
1166
        if not _found:
 
1167
            format = BranchFormat.find_format(a_bzrdir)
 
1168
            assert format.__class__ == self.__class__
804
1169
        transport = a_bzrdir.get_branch_transport(None)
805
 
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
806
 
        return BzrBranch5(_format=self,
 
1170
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1171
                                                     lockdir.LockDir)
 
1172
        return BzrBranch6(_format=self,
807
1173
                          _control_files=control_files,
808
1174
                          a_bzrdir=a_bzrdir,
809
1175
                          _repository=a_bzrdir.find_repository())
810
1176
 
811
 
    def __str__(self):
812
 
        return "Bazaar-NG Metadir branch format 5"
 
1177
    def supports_tags(self):
 
1178
        return True
813
1179
 
814
1180
 
815
1181
class BranchReferenceFormat(BranchFormat):
831
1197
        """See BranchFormat.get_format_description()."""
832
1198
        return "Checkout reference format 1"
833
1199
        
 
1200
    def get_reference(self, a_bzrdir):
 
1201
        """See BranchFormat.get_reference()."""
 
1202
        transport = a_bzrdir.get_branch_transport(None)
 
1203
        return transport.get('location').read()
 
1204
 
834
1205
    def initialize(self, a_bzrdir, target_branch=None):
835
1206
        """Create a branch of this format in a_bzrdir."""
836
1207
        if target_branch is None:
839
1210
            raise errors.UninitializableFormat(self)
840
1211
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
841
1212
        branch_transport = a_bzrdir.get_branch_transport(self)
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()))
 
1213
        branch_transport.put_bytes('location',
 
1214
            target_branch.bzrdir.root_transport.base)
 
1215
        branch_transport.put_bytes('format', self.get_format_string())
845
1216
        return self.open(a_bzrdir, _found=True)
846
1217
 
847
1218
    def __init__(self):
858
1229
            # emit some sort of warning/error to the caller ?!
859
1230
        return clone
860
1231
 
861
 
    def open(self, a_bzrdir, _found=False):
 
1232
    def open(self, a_bzrdir, _found=False, location=None):
862
1233
        """Return the branch that the branch reference in a_bzrdir points at.
863
1234
 
864
1235
        _found is a private parameter, do not use it. It is used to indicate
867
1238
        if not _found:
868
1239
            format = BranchFormat.find_format(a_bzrdir)
869
1240
            assert format.__class__ == self.__class__
870
 
        transport = a_bzrdir.get_branch_transport(None)
871
 
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
1241
        if location is None:
 
1242
            location = self.get_reference(a_bzrdir)
 
1243
        real_bzrdir = bzrdir.BzrDir.open(location)
872
1244
        result = real_bzrdir.open_branch()
873
1245
        # this changes the behaviour of result.clone to create a new reference
874
1246
        # rather than a copy of the content of the branch.
887
1259
__default_format = BzrBranchFormat5()
888
1260
BranchFormat.register_format(__default_format)
889
1261
BranchFormat.register_format(BranchReferenceFormat())
 
1262
BranchFormat.register_format(BzrBranchFormat6())
890
1263
BranchFormat.set_default_format(__default_format)
891
1264
_legacy_formats = [BzrBranchFormat4(),
892
1265
                   ]
899
1272
    it's writable, and can be accessed via the normal filesystem API.
900
1273
    """
901
1274
    
902
 
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
903
 
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
1275
    def __init__(self, _format=None,
904
1276
                 _control_files=None, a_bzrdir=None, _repository=None):
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
 
        """
 
1277
        """Create new branch object at a particular location."""
 
1278
        Branch.__init__(self)
918
1279
        if a_bzrdir is None:
919
 
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
1280
            raise ValueError('a_bzrdir must be supplied')
920
1281
        else:
921
1282
            self.bzrdir = a_bzrdir
922
 
        self._transport = self.bzrdir.transport.clone('..')
923
 
        self._base = self._transport.base
 
1283
        # self._transport used to point to the directory containing the
 
1284
        # control directory, but was not used - now it's just the transport
 
1285
        # for the branch control files.  mbp 20070212
 
1286
        self._base = self.bzrdir.transport.clone('..').base
924
1287
        self._format = _format
925
1288
        if _control_files is None:
926
1289
            raise ValueError('BzrBranch _control_files is None')
927
1290
        self.control_files = _control_files
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)
 
1291
        self._transport = _control_files._transport
954
1292
        self.repository = _repository
955
1293
 
956
1294
    def __str__(self):
958
1296
 
959
1297
    __repr__ = __str__
960
1298
 
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
 
 
976
1299
    def _get_base(self):
 
1300
        """Returns the directory containing the control directory."""
977
1301
        return self._base
978
1302
 
979
1303
    base = property(_get_base, doc="The URL for the root of this branch.")
980
1304
 
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
 
 
1004
1305
    def abspath(self, name):
1005
1306
        """See Branch.abspath."""
1006
1307
        return self.control_files._transport.abspath(name)
1007
1308
 
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
 
 
 
1309
 
 
1310
    @deprecated_method(zero_sixteen)
1024
1311
    @needs_read_lock
1025
1312
    def get_root_id(self):
1026
1313
        """See Branch.get_root_id."""
1030
1317
    def is_locked(self):
1031
1318
        return self.control_files.is_locked()
1032
1319
 
1033
 
    def lock_write(self):
1034
 
        self.repository.lock_write()
 
1320
    def lock_write(self, token=None):
 
1321
        repo_token = self.repository.lock_write()
1035
1322
        try:
1036
 
            self.control_files.lock_write()
 
1323
            token = self.control_files.lock_write(token=token)
1037
1324
        except:
1038
1325
            self.repository.unlock()
1039
1326
            raise
 
1327
        return token
1040
1328
 
1041
1329
    def lock_read(self):
1042
1330
        self.repository.lock_read()
1052
1340
            self.control_files.unlock()
1053
1341
        finally:
1054
1342
            self.repository.unlock()
 
1343
        if not self.control_files.is_locked():
 
1344
            # we just released the lock
 
1345
            self._clear_cached_state()
1055
1346
        
1056
1347
    def peek_lock_mode(self):
1057
1348
        if self.control_files._lock_count == 0:
1070
1361
    @needs_write_lock
1071
1362
    def append_revision(self, *revision_ids):
1072
1363
        """See Branch.append_revision."""
 
1364
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1073
1365
        for revision_id in revision_ids:
 
1366
            _mod_revision.check_not_reserved_id(revision_id)
1074
1367
            mutter("add {%s} to revision-history" % revision_id)
1075
1368
        rev_history = self.revision_history()
1076
1369
        rev_history.extend(revision_ids)
1077
1370
        self.set_revision_history(rev_history)
1078
1371
 
 
1372
    def _write_revision_history(self, history):
 
1373
        """Factored out of set_revision_history.
 
1374
 
 
1375
        This performs the actual writing to disk.
 
1376
        It is intended to be called by BzrBranch5.set_revision_history."""
 
1377
        self.control_files.put_bytes(
 
1378
            'revision-history', '\n'.join(history))
 
1379
 
1079
1380
    @needs_write_lock
1080
1381
    def set_revision_history(self, rev_history):
1081
1382
        """See Branch.set_revision_history."""
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)
 
1383
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
 
1384
        self._clear_cached_state()
 
1385
        self._write_revision_history(rev_history)
 
1386
        self._cache_revision_history(rev_history)
 
1387
        for hook in Branch.hooks['set_rh']:
 
1388
            hook(self, rev_history)
1114
1389
 
1115
1390
    @needs_write_lock
1116
 
    def generate_revision_history(self, revision_id, last_rev=None, 
1117
 
        other_branch=None):
1118
 
        """Create a new revision history that will finish with revision_id.
1119
 
        
1120
 
        :param revision_id: the new tip to use.
1121
 
        :param last_rev: The previous last_revision. If not None, then this
1122
 
            must be a ancestory of revision_id, or DivergedBranches is raised.
1123
 
        :param other_branch: The other branch that DivergedBranches should
1124
 
            raise with respect to.
1125
 
        """
 
1391
    def set_last_revision_info(self, revno, revision_id):
 
1392
        revision_id = osutils.safe_revision_id(revision_id)
 
1393
        history = self._lefthand_history(revision_id)
 
1394
        assert len(history) == revno, '%d != %d' % (len(history), revno)
 
1395
        self.set_revision_history(history)
 
1396
 
 
1397
    def _gen_revision_history(self):
 
1398
        history = self.control_files.get('revision-history').read().split('\n')
 
1399
        if history[-1:] == ['']:
 
1400
            # There shouldn't be a trailing newline, but just in case.
 
1401
            history.pop()
 
1402
        return history
 
1403
 
 
1404
    def _lefthand_history(self, revision_id, last_rev=None,
 
1405
                          other_branch=None):
1126
1406
        # stop_revision must be a descendant of last_revision
1127
1407
        stop_graph = self.repository.get_revision_graph(revision_id)
1128
1408
        if last_rev is not None and last_rev not in stop_graph:
1131
1411
        # make a new revision history from the graph
1132
1412
        current_rev_id = revision_id
1133
1413
        new_history = []
1134
 
        while current_rev_id not in (None, revision.NULL_REVISION):
 
1414
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1135
1415
            new_history.append(current_rev_id)
1136
1416
            current_rev_id_parents = stop_graph[current_rev_id]
1137
1417
            try:
1139
1419
            except IndexError:
1140
1420
                current_rev_id = None
1141
1421
        new_history.reverse()
1142
 
        self.set_revision_history(new_history)
 
1422
        return new_history
 
1423
 
 
1424
    @needs_write_lock
 
1425
    def generate_revision_history(self, revision_id, last_rev=None,
 
1426
        other_branch=None):
 
1427
        """Create a new revision history that will finish with revision_id.
 
1428
 
 
1429
        :param revision_id: the new tip to use.
 
1430
        :param last_rev: The previous last_revision. If not None, then this
 
1431
            must be a ancestory of revision_id, or DivergedBranches is raised.
 
1432
        :param other_branch: The other branch that DivergedBranches should
 
1433
            raise with respect to.
 
1434
        """
 
1435
        revision_id = osutils.safe_revision_id(revision_id)
 
1436
        self.set_revision_history(self._lefthand_history(revision_id,
 
1437
            last_rev, other_branch))
1143
1438
 
1144
1439
    @needs_write_lock
1145
1440
    def update_revisions(self, other, stop_revision=None):
1151
1446
                if stop_revision is None:
1152
1447
                    # if there are no commits, we're done.
1153
1448
                    return
 
1449
            else:
 
1450
                stop_revision = osutils.safe_revision_id(stop_revision)
1154
1451
            # whats the current last revision, before we fetch [and change it
1155
1452
            # possibly]
1156
1453
            last_rev = self.last_revision()
1181
1478
        return self.bzrdir.open_workingtree()
1182
1479
 
1183
1480
    @needs_write_lock
1184
 
    def pull(self, source, overwrite=False, stop_revision=None):
1185
 
        """See Branch.pull."""
 
1481
    def pull(self, source, overwrite=False, stop_revision=None,
 
1482
             _hook_master=None, run_hooks=True):
 
1483
        """See Branch.pull.
 
1484
 
 
1485
        :param _hook_master: Private parameter - set the branch to 
 
1486
            be supplied as the master to push hooks.
 
1487
        :param run_hooks: Private parameter - if false, this branch
 
1488
            is being called because it's the master of the primary branch,
 
1489
            so it should not run its hooks.
 
1490
        """
 
1491
        result = PullResult()
 
1492
        result.source_branch = source
 
1493
        result.target_branch = self
1186
1494
        source.lock_read()
1187
1495
        try:
1188
 
            old_count = len(self.revision_history())
 
1496
            result.old_revno, result.old_revid = self.last_revision_info()
1189
1497
            try:
1190
 
                self.update_revisions(source,stop_revision)
 
1498
                self.update_revisions(source, stop_revision)
1191
1499
            except DivergedBranches:
1192
1500
                if not overwrite:
1193
1501
                    raise
1194
1502
            if overwrite:
1195
 
                self.set_revision_history(source.revision_history())
1196
 
            new_count = len(self.revision_history())
1197
 
            return new_count - old_count
 
1503
                if stop_revision is None:
 
1504
                    stop_revision = source.last_revision()
 
1505
                self.generate_revision_history(stop_revision)
 
1506
            result.tag_conflicts = source.tags.merge_to(self.tags)
 
1507
            result.new_revno, result.new_revid = self.last_revision_info()
 
1508
            if _hook_master:
 
1509
                result.master_branch = _hook_master
 
1510
                result.local_branch = self
 
1511
            else:
 
1512
                result.master_branch = self
 
1513
                result.local_branch = None
 
1514
            if run_hooks:
 
1515
                for hook in Branch.hooks['post_pull']:
 
1516
                    hook(result)
1198
1517
        finally:
1199
1518
            source.unlock()
 
1519
        return result
 
1520
 
 
1521
    def _get_parent_location(self):
 
1522
        _locs = ['parent', 'pull', 'x-pull']
 
1523
        for l in _locs:
 
1524
            try:
 
1525
                return self.control_files.get(l).read().strip('\n')
 
1526
            except NoSuchFile:
 
1527
                pass
 
1528
        return None
 
1529
 
 
1530
    @needs_read_lock
 
1531
    def push(self, target, overwrite=False, stop_revision=None,
 
1532
             _override_hook_source_branch=None):
 
1533
        """See Branch.push.
 
1534
 
 
1535
        This is the basic concrete implementation of push()
 
1536
 
 
1537
        :param _override_hook_source_branch: If specified, run
 
1538
        the hooks passing this Branch as the source, rather than self.  
 
1539
        This is for use of RemoteBranch, where push is delegated to the
 
1540
        underlying vfs-based Branch. 
 
1541
        """
 
1542
        # TODO: Public option to disable running hooks - should be trivial but
 
1543
        # needs tests.
 
1544
        target.lock_write()
 
1545
        try:
 
1546
            result = self._push_with_bound_branches(target, overwrite,
 
1547
                    stop_revision,
 
1548
                    _override_hook_source_branch=_override_hook_source_branch)
 
1549
            return result
 
1550
        finally:
 
1551
            target.unlock()
 
1552
 
 
1553
    def _push_with_bound_branches(self, target, overwrite,
 
1554
            stop_revision,
 
1555
            _override_hook_source_branch=None):
 
1556
        """Push from self into target, and into target's master if any.
 
1557
        
 
1558
        This is on the base BzrBranch class even though it doesn't support 
 
1559
        bound branches because the *target* might be bound.
 
1560
        """
 
1561
        def _run_hooks():
 
1562
            if _override_hook_source_branch:
 
1563
                result.source_branch = _override_hook_source_branch
 
1564
            for hook in Branch.hooks['post_push']:
 
1565
                hook(result)
 
1566
 
 
1567
        bound_location = target.get_bound_location()
 
1568
        if bound_location and target.base != bound_location:
 
1569
            # there is a master branch.
 
1570
            #
 
1571
            # XXX: Why the second check?  Is it even supported for a branch to
 
1572
            # be bound to itself? -- mbp 20070507
 
1573
            master_branch = target.get_master_branch()
 
1574
            master_branch.lock_write()
 
1575
            try:
 
1576
                # push into the master from this branch.
 
1577
                self._basic_push(master_branch, overwrite, stop_revision)
 
1578
                # and push into the target branch from this. Note that we push from
 
1579
                # this branch again, because its considered the highest bandwidth
 
1580
                # repository.
 
1581
                result = self._basic_push(target, overwrite, stop_revision)
 
1582
                result.master_branch = master_branch
 
1583
                result.local_branch = target
 
1584
                _run_hooks()
 
1585
                return result
 
1586
            finally:
 
1587
                master_branch.unlock()
 
1588
        else:
 
1589
            # no master branch
 
1590
            result = self._basic_push(target, overwrite, stop_revision)
 
1591
            # TODO: Why set master_branch and local_branch if there's no
 
1592
            # binding?  Maybe cleaner to just leave them unset? -- mbp
 
1593
            # 20070504
 
1594
            result.master_branch = target
 
1595
            result.local_branch = None
 
1596
            _run_hooks()
 
1597
            return result
 
1598
 
 
1599
    def _basic_push(self, target, overwrite, stop_revision):
 
1600
        """Basic implementation of push without bound branches or hooks.
 
1601
 
 
1602
        Must be called with self read locked and target write locked.
 
1603
        """
 
1604
        result = PushResult()
 
1605
        result.source_branch = self
 
1606
        result.target_branch = target
 
1607
        result.old_revno, result.old_revid = target.last_revision_info()
 
1608
        try:
 
1609
            target.update_revisions(self, stop_revision)
 
1610
        except DivergedBranches:
 
1611
            if not overwrite:
 
1612
                raise
 
1613
        if overwrite:
 
1614
            target.set_revision_history(self.revision_history())
 
1615
        result.tag_conflicts = self.tags.merge_to(target.tags)
 
1616
        result.new_revno, result.new_revid = target.last_revision_info()
 
1617
        return result
1200
1618
 
1201
1619
    def get_parent(self):
1202
1620
        """See Branch.get_parent."""
1203
1621
 
1204
 
        _locs = ['parent', 'pull', 'x-pull']
1205
1622
        assert self.base[-1] == '/'
1206
 
        for l in _locs:
1207
 
            try:
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)
1219
 
        return None
1220
 
 
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
 
1623
        parent = self._get_parent_location()
 
1624
        if parent is None:
 
1625
            return parent
 
1626
        # This is an old-format absolute path to a local branch
 
1627
        # turn it into a url
 
1628
        if parent.startswith('/'):
 
1629
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1630
        try:
 
1631
            return urlutils.join(self.base[:-1], parent)
 
1632
        except errors.InvalidURLJoin, e:
 
1633
            raise errors.InaccessibleParent(parent, self.base)
1225
1634
 
1226
1635
    def set_push_location(self, location):
1227
1636
        """See Branch.set_push_location."""
1228
 
        self.get_config().set_user_option('push_location', location, 
1229
 
                                          local=True)
 
1637
        self.get_config().set_user_option(
 
1638
            'push_location', location,
 
1639
            store=_mod_config.STORE_LOCATION_NORECURSE)
1230
1640
 
1231
1641
    @needs_write_lock
1232
1642
    def set_parent(self, url):
1236
1646
        # FIXUP this and get_parent in a future branch format bump:
1237
1647
        # read and rewrite the file, and have the new format code read
1238
1648
        # using .get not .get_utf8. RBC 20060125
1239
 
        if url is None:
1240
 
            self.control_files._transport.delete('parent')
1241
 
        else:
 
1649
        if url is not None:
1242
1650
            if isinstance(url, unicode):
1243
1651
                try: 
1244
1652
                    url = url.encode('ascii')
1245
1653
                except UnicodeEncodeError:
1246
 
                    raise bzrlib.errors.InvalidURL(url,
 
1654
                    raise errors.InvalidURL(url,
1247
1655
                        "Urls must be 7-bit ascii, "
1248
1656
                        "use bzrlib.urlutils.escape")
1249
 
                    
1250
1657
            url = urlutils.relative_url(self.base, url)
1251
 
            self.control_files.put('parent', url + '\n')
 
1658
        self._set_parent_location(url)
 
1659
 
 
1660
    def _set_parent_location(self, url):
 
1661
        if url is None:
 
1662
            self.control_files._transport.delete('parent')
 
1663
        else:
 
1664
            assert isinstance(url, str)
 
1665
            self.control_files.put_bytes('parent', url + '\n')
1252
1666
 
1253
1667
    @deprecated_function(zero_nine)
1254
1668
    def tree_config(self):
1274
1688
                                         _repository=_repository)
1275
1689
        
1276
1690
    @needs_write_lock
1277
 
    def pull(self, source, overwrite=False, stop_revision=None):
1278
 
        """Updates branch.pull to be bound branch aware."""
 
1691
    def pull(self, source, overwrite=False, stop_revision=None,
 
1692
             run_hooks=True):
 
1693
        """Pull from source into self, updating my master if any.
 
1694
        
 
1695
        :param run_hooks: Private parameter - if false, this branch
 
1696
            is being called because it's the master of the primary branch,
 
1697
            so it should not run its hooks.
 
1698
        """
1279
1699
        bound_location = self.get_bound_location()
1280
 
        if source.base != bound_location:
 
1700
        master_branch = None
 
1701
        if bound_location and source.base != bound_location:
1281
1702
            # not pulling from master, so we need to update master.
1282
1703
            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)
 
1704
            master_branch.lock_write()
 
1705
        try:
 
1706
            if master_branch:
 
1707
                # pull from source into master.
 
1708
                master_branch.pull(source, overwrite, stop_revision,
 
1709
                    run_hooks=False)
 
1710
            return super(BzrBranch5, self).pull(source, overwrite,
 
1711
                stop_revision, _hook_master=master_branch,
 
1712
                run_hooks=run_hooks)
 
1713
        finally:
 
1714
            if master_branch:
 
1715
                master_branch.unlock()
1287
1716
 
1288
1717
    def get_bound_location(self):
1289
1718
        try:
1328
1757
 
1329
1758
    @needs_write_lock
1330
1759
    def bind(self, other):
1331
 
        """Bind the local branch the other branch.
 
1760
        """Bind this branch to the branch other.
1332
1761
 
 
1762
        This does not push or pull data between the branches, though it does
 
1763
        check for divergence to raise an error when the branches are not
 
1764
        either the same, or one a prefix of the other. That behaviour may not
 
1765
        be useful, so that check may be removed in future.
 
1766
        
1333
1767
        :param other: The branch to bind to
1334
1768
        :type other: Branch
1335
1769
        """
1340
1774
        #       but binding itself may not be.
1341
1775
        #       Since we *have* to check at commit time, we don't
1342
1776
        #       *need* to check here
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
 
 
 
1777
 
 
1778
        # we want to raise diverged if:
 
1779
        # last_rev is not in the other_last_rev history, AND
 
1780
        # other_last_rev is not in our history, and do it without pulling
 
1781
        # history around
 
1782
        last_rev = self.last_revision()
 
1783
        if last_rev is not None:
 
1784
            other.lock_read()
 
1785
            try:
 
1786
                other_last_rev = other.last_revision()
 
1787
                if other_last_rev is not None:
 
1788
                    # neither branch is new, we have to do some work to
 
1789
                    # ascertain diversion.
 
1790
                    remote_graph = other.repository.get_revision_graph(
 
1791
                        other_last_rev)
 
1792
                    local_graph = self.repository.get_revision_graph(last_rev)
 
1793
                    if (last_rev not in remote_graph and
 
1794
                        other_last_rev not in local_graph):
 
1795
                        raise errors.DivergedBranches(self, other)
 
1796
            finally:
 
1797
                other.unlock()
1372
1798
        self.set_bound_location(other.base)
1373
1799
 
1374
1800
    @needs_write_lock
1393
1819
        return None
1394
1820
 
1395
1821
 
 
1822
class BzrBranchExperimental(BzrBranch5):
 
1823
    """Bzr experimental branch format
 
1824
 
 
1825
    This format has:
 
1826
     - a revision-history file.
 
1827
     - a format string
 
1828
     - a lock dir guarding the branch itself
 
1829
     - all of this stored in a branch/ subdirectory
 
1830
     - works with shared repositories.
 
1831
     - a tag dictionary in the branch
 
1832
 
 
1833
    This format is new in bzr 0.15, but shouldn't be used for real data, 
 
1834
    only for testing.
 
1835
 
 
1836
    This class acts as it's own BranchFormat.
 
1837
    """
 
1838
 
 
1839
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1840
 
 
1841
    @classmethod
 
1842
    def get_format_string(cls):
 
1843
        """See BranchFormat.get_format_string()."""
 
1844
        return "Bazaar-NG branch format experimental\n"
 
1845
 
 
1846
    @classmethod
 
1847
    def get_format_description(cls):
 
1848
        """See BranchFormat.get_format_description()."""
 
1849
        return "Experimental branch format"
 
1850
 
 
1851
    @classmethod
 
1852
    def get_reference(cls, a_bzrdir):
 
1853
        """Get the target reference of the branch in a_bzrdir.
 
1854
 
 
1855
        format probing must have been completed before calling
 
1856
        this method - it is assumed that the format of the branch
 
1857
        in a_bzrdir is correct.
 
1858
 
 
1859
        :param a_bzrdir: The bzrdir to get the branch data from.
 
1860
        :return: None if the branch is not a reference branch.
 
1861
        """
 
1862
        return None
 
1863
 
 
1864
    @classmethod
 
1865
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
 
1866
            lock_class):
 
1867
        branch_transport = a_bzrdir.get_branch_transport(cls)
 
1868
        control_files = lockable_files.LockableFiles(branch_transport,
 
1869
            lock_filename, lock_class)
 
1870
        control_files.create_lock()
 
1871
        control_files.lock_write()
 
1872
        try:
 
1873
            for filename, content in utf8_files:
 
1874
                control_files.put_utf8(filename, content)
 
1875
        finally:
 
1876
            control_files.unlock()
 
1877
        
 
1878
    @classmethod
 
1879
    def initialize(cls, a_bzrdir):
 
1880
        """Create a branch of this format in a_bzrdir."""
 
1881
        utf8_files = [('format', cls.get_format_string()),
 
1882
                      ('revision-history', ''),
 
1883
                      ('branch-name', ''),
 
1884
                      ('tags', ''),
 
1885
                      ]
 
1886
        cls._initialize_control_files(a_bzrdir, utf8_files,
 
1887
            'lock', lockdir.LockDir)
 
1888
        return cls.open(a_bzrdir, _found=True)
 
1889
 
 
1890
    @classmethod
 
1891
    def open(cls, a_bzrdir, _found=False):
 
1892
        """Return the branch object for a_bzrdir
 
1893
 
 
1894
        _found is a private parameter, do not use it. It is used to indicate
 
1895
               if format probing has already be done.
 
1896
        """
 
1897
        if not _found:
 
1898
            format = BranchFormat.find_format(a_bzrdir)
 
1899
            assert format.__class__ == cls
 
1900
        transport = a_bzrdir.get_branch_transport(None)
 
1901
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
1902
                                                     lockdir.LockDir)
 
1903
        return cls(_format=cls,
 
1904
            _control_files=control_files,
 
1905
            a_bzrdir=a_bzrdir,
 
1906
            _repository=a_bzrdir.find_repository())
 
1907
 
 
1908
    @classmethod
 
1909
    def is_supported(cls):
 
1910
        return True
 
1911
 
 
1912
    def _make_tags(self):
 
1913
        return BasicTags(self)
 
1914
 
 
1915
    @classmethod
 
1916
    def supports_tags(cls):
 
1917
        return True
 
1918
 
 
1919
 
 
1920
BranchFormat.register_format(BzrBranchExperimental)
 
1921
 
 
1922
 
 
1923
class BzrBranch6(BzrBranch5):
 
1924
 
 
1925
    @needs_read_lock
 
1926
    def last_revision_info(self):
 
1927
        revision_string = self.control_files.get('last-revision').read()
 
1928
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
 
1929
        revision_id = cache_utf8.get_cached_utf8(revision_id)
 
1930
        revno = int(revno)
 
1931
        return revno, revision_id
 
1932
 
 
1933
    def last_revision(self):
 
1934
        """Return last revision id, or None"""
 
1935
        revision_id = self.last_revision_info()[1]
 
1936
        if revision_id == _mod_revision.NULL_REVISION:
 
1937
            revision_id = None
 
1938
        return revision_id
 
1939
 
 
1940
    def _write_last_revision_info(self, revno, revision_id):
 
1941
        """Simply write out the revision id, with no checks.
 
1942
 
 
1943
        Use set_last_revision_info to perform this safely.
 
1944
 
 
1945
        Does not update the revision_history cache.
 
1946
        Intended to be called by set_last_revision_info and
 
1947
        _write_revision_history.
 
1948
        """
 
1949
        if revision_id is None:
 
1950
            revision_id = 'null:'
 
1951
        out_string = '%d %s\n' % (revno, revision_id)
 
1952
        self.control_files.put_bytes('last-revision', out_string)
 
1953
 
 
1954
    @needs_write_lock
 
1955
    def set_last_revision_info(self, revno, revision_id):
 
1956
        revision_id = osutils.safe_revision_id(revision_id)
 
1957
        if self._get_append_revisions_only():
 
1958
            self._check_history_violation(revision_id)
 
1959
        self._write_last_revision_info(revno, revision_id)
 
1960
        self._clear_cached_state()
 
1961
 
 
1962
    def _check_history_violation(self, revision_id):
 
1963
        last_revision = self.last_revision()
 
1964
        if last_revision is None:
 
1965
            return
 
1966
        if last_revision not in self._lefthand_history(revision_id):
 
1967
            raise errors.AppendRevisionsOnlyViolation(self.base)
 
1968
 
 
1969
    def _gen_revision_history(self):
 
1970
        """Generate the revision history from last revision
 
1971
        """
 
1972
        history = list(self.repository.iter_reverse_revision_history(
 
1973
            self.last_revision()))
 
1974
        history.reverse()
 
1975
        return history
 
1976
 
 
1977
    def _write_revision_history(self, history):
 
1978
        """Factored out of set_revision_history.
 
1979
 
 
1980
        This performs the actual writing to disk, with format-specific checks.
 
1981
        It is intended to be called by BzrBranch5.set_revision_history.
 
1982
        """
 
1983
        if len(history) == 0:
 
1984
            last_revision = 'null:'
 
1985
        else:
 
1986
            if history != self._lefthand_history(history[-1]):
 
1987
                raise errors.NotLefthandHistory(history)
 
1988
            last_revision = history[-1]
 
1989
        if self._get_append_revisions_only():
 
1990
            self._check_history_violation(last_revision)
 
1991
        self._write_last_revision_info(len(history), last_revision)
 
1992
 
 
1993
    @needs_write_lock
 
1994
    def append_revision(self, *revision_ids):
 
1995
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
 
1996
        if len(revision_ids) == 0:
 
1997
            return
 
1998
        prev_revno, prev_revision = self.last_revision_info()
 
1999
        for revision in self.repository.get_revisions(revision_ids):
 
2000
            if prev_revision == _mod_revision.NULL_REVISION:
 
2001
                if revision.parent_ids != []:
 
2002
                    raise errors.NotLeftParentDescendant(self, prev_revision,
 
2003
                                                         revision.revision_id)
 
2004
            else:
 
2005
                if revision.parent_ids[0] != prev_revision:
 
2006
                    raise errors.NotLeftParentDescendant(self, prev_revision,
 
2007
                                                         revision.revision_id)
 
2008
            prev_revision = revision.revision_id
 
2009
        self.set_last_revision_info(prev_revno + len(revision_ids),
 
2010
                                    revision_ids[-1])
 
2011
 
 
2012
    @needs_write_lock
 
2013
    def _set_parent_location(self, url):
 
2014
        """Set the parent branch"""
 
2015
        self._set_config_location('parent_location', url, make_relative=True)
 
2016
 
 
2017
    @needs_read_lock
 
2018
    def _get_parent_location(self):
 
2019
        """Set the parent branch"""
 
2020
        return self._get_config_location('parent_location')
 
2021
 
 
2022
    def set_push_location(self, location):
 
2023
        """See Branch.set_push_location."""
 
2024
        self._set_config_location('push_location', location)
 
2025
 
 
2026
    def set_bound_location(self, location):
 
2027
        """See Branch.set_push_location."""
 
2028
        result = None
 
2029
        config = self.get_config()
 
2030
        if location is None:
 
2031
            if config.get_user_option('bound') != 'True':
 
2032
                return False
 
2033
            else:
 
2034
                config.set_user_option('bound', 'False')
 
2035
                return True
 
2036
        else:
 
2037
            self._set_config_location('bound_location', location,
 
2038
                                      config=config)
 
2039
            config.set_user_option('bound', 'True')
 
2040
        return True
 
2041
 
 
2042
    def _get_bound_location(self, bound):
 
2043
        """Return the bound location in the config file.
 
2044
 
 
2045
        Return None if the bound parameter does not match"""
 
2046
        config = self.get_config()
 
2047
        config_bound = (config.get_user_option('bound') == 'True')
 
2048
        if config_bound != bound:
 
2049
            return None
 
2050
        return self._get_config_location('bound_location', config=config)
 
2051
 
 
2052
    def get_bound_location(self):
 
2053
        """See Branch.set_push_location."""
 
2054
        return self._get_bound_location(True)
 
2055
 
 
2056
    def get_old_bound_location(self):
 
2057
        """See Branch.get_old_bound_location"""
 
2058
        return self._get_bound_location(False)
 
2059
 
 
2060
    def set_append_revisions_only(self, enabled):
 
2061
        if enabled:
 
2062
            value = 'True'
 
2063
        else:
 
2064
            value = 'False'
 
2065
        self.get_config().set_user_option('append_revisions_only', value)
 
2066
 
 
2067
    def _get_append_revisions_only(self):
 
2068
        value = self.get_config().get_user_option('append_revisions_only')
 
2069
        return value == 'True'
 
2070
 
 
2071
    def _synchronize_history(self, destination, revision_id):
 
2072
        """Synchronize last revision and revision history between branches.
 
2073
 
 
2074
        This version is most efficient when the destination is also a
 
2075
        BzrBranch6, but works for BzrBranch5, as long as the destination's
 
2076
        repository contains all the lefthand ancestors of the intended
 
2077
        last_revision.  If not, set_last_revision_info will fail.
 
2078
 
 
2079
        :param destination: The branch to copy the history into
 
2080
        :param revision_id: The revision-id to truncate history at.  May
 
2081
          be None to copy complete history.
 
2082
        """
 
2083
        if revision_id is None:
 
2084
            revno, revision_id = self.last_revision_info()
 
2085
        else:
 
2086
            revno = self.revision_id_to_revno(revision_id)
 
2087
        destination.set_last_revision_info(revno, revision_id)
 
2088
 
 
2089
    def _make_tags(self):
 
2090
        return BasicTags(self)
 
2091
 
 
2092
 
1396
2093
class BranchTestProviderAdapter(object):
1397
2094
    """A tool to generate a suite testing multiple branch formats at once.
1398
2095
 
1402
2099
    easy to identify.
1403
2100
    """
1404
2101
 
1405
 
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2102
    def __init__(self, transport_server, transport_readonly_server, formats,
 
2103
        vfs_transport_factory=None):
1406
2104
        self._transport_server = transport_server
1407
2105
        self._transport_readonly_server = transport_readonly_server
1408
2106
        self._formats = formats
1416
2114
            new_test.bzrdir_format = bzrdir_format
1417
2115
            new_test.branch_format = branch_format
1418
2116
            def make_new_test_id():
1419
 
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
2117
                # the format can be either a class or an instance
 
2118
                name = getattr(branch_format, '__name__',
 
2119
                        branch_format.__class__.__name__)
 
2120
                new_id = "%s(%s)" % (new_test.id(), name)
1420
2121
                return lambda: new_id
1421
2122
            new_test.id = make_new_test_id()
1422
2123
            result.addTest(new_test)
1423
2124
        return result
1424
2125
 
1425
2126
 
 
2127
######################################################################
 
2128
# results of operations
 
2129
 
 
2130
 
 
2131
class _Result(object):
 
2132
 
 
2133
    def _show_tag_conficts(self, to_file):
 
2134
        if not getattr(self, 'tag_conflicts', None):
 
2135
            return
 
2136
        to_file.write('Conflicting tags:\n')
 
2137
        for name, value1, value2 in self.tag_conflicts:
 
2138
            to_file.write('    %s\n' % (name, ))
 
2139
 
 
2140
 
 
2141
class PullResult(_Result):
 
2142
    """Result of a Branch.pull operation.
 
2143
 
 
2144
    :ivar old_revno: Revision number before pull.
 
2145
    :ivar new_revno: Revision number after pull.
 
2146
    :ivar old_revid: Tip revision id before pull.
 
2147
    :ivar new_revid: Tip revision id after pull.
 
2148
    :ivar source_branch: Source (local) branch object.
 
2149
    :ivar master_branch: Master branch of the target, or None.
 
2150
    :ivar target_branch: Target/destination branch object.
 
2151
    """
 
2152
 
 
2153
    def __int__(self):
 
2154
        # DEPRECATED: pull used to return the change in revno
 
2155
        return self.new_revno - self.old_revno
 
2156
 
 
2157
    def report(self, to_file):
 
2158
        if self.old_revid == self.new_revid:
 
2159
            to_file.write('No revisions to pull.\n')
 
2160
        else:
 
2161
            to_file.write('Now on revision %d.\n' % self.new_revno)
 
2162
        self._show_tag_conficts(to_file)
 
2163
 
 
2164
 
 
2165
class PushResult(_Result):
 
2166
    """Result of a Branch.push operation.
 
2167
 
 
2168
    :ivar old_revno: Revision number before push.
 
2169
    :ivar new_revno: Revision number after push.
 
2170
    :ivar old_revid: Tip revision id before push.
 
2171
    :ivar new_revid: Tip revision id after push.
 
2172
    :ivar source_branch: Source branch object.
 
2173
    :ivar master_branch: Master branch of the target, or None.
 
2174
    :ivar target_branch: Target/destination branch object.
 
2175
    """
 
2176
 
 
2177
    def __int__(self):
 
2178
        # DEPRECATED: push used to return the change in revno
 
2179
        return self.new_revno - self.old_revno
 
2180
 
 
2181
    def report(self, to_file):
 
2182
        """Write a human-readable description of the result."""
 
2183
        if self.old_revid == self.new_revid:
 
2184
            to_file.write('No new revisions to push.\n')
 
2185
        else:
 
2186
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
 
2187
        self._show_tag_conficts(to_file)
 
2188
 
 
2189
 
1426
2190
class BranchCheckResult(object):
1427
2191
    """Results of checking branch consistency.
1428
2192
 
1443
2207
             self.branch._format)
1444
2208
 
1445
2209
 
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)
 
2210
class Converter5to6(object):
 
2211
    """Perform an in-place upgrade of format 5 to format 6"""
 
2212
 
 
2213
    def convert(self, branch):
 
2214
        # Data for 5 and 6 can peacefully coexist.
 
2215
        format = BzrBranchFormat6()
 
2216
        new_branch = format.open(branch.bzrdir, _found=True)
 
2217
 
 
2218
        # Copy source data into target
 
2219
        new_branch.set_last_revision_info(*branch.last_revision_info())
 
2220
        new_branch.set_parent(branch.get_parent())
 
2221
        new_branch.set_bound_location(branch.get_bound_location())
 
2222
        new_branch.set_push_location(branch.get_push_location())
 
2223
 
 
2224
        # New branch has no tags by default
 
2225
        new_branch.tags._set_tag_dict({})
 
2226
 
 
2227
        # Copying done; now update target format
 
2228
        new_branch.control_files.put_utf8('format',
 
2229
            format.get_format_string())
 
2230
 
 
2231
        # Clean up old files
 
2232
        new_branch.control_files._transport.delete('revision-history')
 
2233
        try:
 
2234
            branch.set_parent(None)
 
2235
        except NoSuchFile:
 
2236
            pass
 
2237
        branch.set_bound_location(None)