~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2006-02-22 04:29:54 UTC
  • mfrom: (1566 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1569.
  • Revision ID: mbp@sourcefrog.net-20060222042954-60333f08dd56a646
[merge] from bzr.dev before integration
Fix undefined ordering in sign_my_revisions breaking tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
18
from copy import deepcopy
19
19
from cStringIO import StringIO
 
20
import errno
 
21
import os
 
22
import shutil
 
23
import sys
20
24
from unittest import TestSuite
21
25
from warnings import warn
 
26
try:
 
27
    import xml.sax.saxutils
 
28
except ImportError:
 
29
    raise ImportError("We were unable to import 'xml.sax.saxutils',"
 
30
                      " most likely you have an xml.pyc or xml.pyo file"
 
31
                      " lying around in your bzrlib directory."
 
32
                      " Please remove it.")
 
33
 
22
34
 
23
35
import bzrlib
24
 
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
25
 
        tree, \
26
 
        ui, \
27
 
        urlutils
 
36
import bzrlib.bzrdir as bzrdir
28
37
from bzrlib.config import TreeConfig
29
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
39
from bzrlib.delta import compare_trees
30
40
import bzrlib.errors as errors
31
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
32
 
                           HistoryMissing, InvalidRevisionId, 
33
 
                           InvalidRevisionNumber, LockError, NoSuchFile, 
34
 
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
35
 
                           NotBranchError, UninitializableFormat, 
36
 
                           UnlistableStore, UnlistableBranch, 
37
 
                           )
38
 
from bzrlib.lockable_files import LockableFiles, TransportLock
39
 
from bzrlib.symbol_versioning import (deprecated_function,
40
 
                                      deprecated_method,
41
 
                                      DEPRECATED_PARAMETER,
42
 
                                      deprecated_passed,
43
 
                                      zero_eight, zero_nine,
44
 
                                      )
 
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
42
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
43
                           DivergedBranches, LockError,
 
44
                           UninitializableFormat,
 
45
                           UnlistableStore,
 
46
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
47
                           NoWorkingTree)
 
48
import bzrlib.inventory as inventory
 
49
from bzrlib.inventory import Inventory
 
50
from bzrlib.lockable_files import LockableFiles
 
51
from bzrlib.osutils import (isdir, quotefn,
 
52
                            rename, splitpath, sha_file,
 
53
                            file_kind, abspath, normpath, pathjoin,
 
54
                            safe_unicode,
 
55
                            )
 
56
from bzrlib.textui import show_status
45
57
from bzrlib.trace import mutter, note
 
58
from bzrlib.tree import EmptyTree, RevisionTree
 
59
from bzrlib.repository import Repository
 
60
from bzrlib.revision import (
 
61
                             get_intervening_revisions,
 
62
                             is_ancestor,
 
63
                             NULL_REVISION,
 
64
                             Revision,
 
65
                             )
 
66
from bzrlib.store import copy_all
 
67
from bzrlib.symbol_versioning import *
 
68
import bzrlib.transactions as transactions
 
69
from bzrlib.transport import Transport, get_transport
 
70
from bzrlib.tree import EmptyTree, RevisionTree
 
71
import bzrlib.ui
 
72
import bzrlib.xml5
46
73
 
47
74
 
48
75
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
75
102
    def __init__(self, *ignored, **ignored_too):
76
103
        raise NotImplementedError('The Branch class is abstract')
77
104
 
78
 
    def break_lock(self):
79
 
        """Break a lock if one is present from another instance.
80
 
 
81
 
        Uses the ui factory to ask for confirmation if the lock may be from
82
 
        an active process.
83
 
 
84
 
        This will probe the repository for its lock as well.
85
 
        """
86
 
        self.control_files.break_lock()
87
 
        self.repository.break_lock()
88
 
        master = self.get_master_branch()
89
 
        if master is not None:
90
 
            master.break_lock()
91
 
 
92
105
    @staticmethod
93
106
    @deprecated_method(zero_eight)
94
107
    def open_downlevel(base):
97
110
        
98
111
    @staticmethod
99
112
    def open(base, _unsupported=False):
100
 
        """Open the branch rooted at base.
 
113
        """Open the repository rooted at base.
101
114
 
102
 
        For instance, if the branch is at URL/.bzr/branch,
103
 
        Branch.open(URL) -> a Branch instance.
 
115
        For instance, if the repository is at URL/.bzr/repository,
 
116
        Repository.open(URL) -> a Repository instance.
104
117
        """
105
118
        control = bzrdir.BzrDir.open(base, _unsupported)
106
119
        return control.open_branch(_unsupported)
138
151
        warn('%s is deprecated' % self.setup_caching)
139
152
        self.cache_root = cache_root
140
153
 
141
 
    def get_config(self):
142
 
        return bzrlib.config.BranchConfig(self)
143
 
 
144
154
    def _get_nick(self):
145
 
        return self.get_config().get_nickname()
 
155
        cfg = self.tree_config()
 
156
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
146
157
 
147
158
    def _set_nick(self, nick):
148
 
        self.get_config().set_user_option('nickname', nick)
 
159
        cfg = self.tree_config()
 
160
        cfg.set_option(nick, "nickname")
 
161
        assert cfg.get_option("nickname") == nick
149
162
 
150
163
    nick = property(_get_nick, _set_nick)
151
 
 
152
 
    def is_locked(self):
153
 
        raise NotImplementedError('is_locked is abstract')
154
 
 
 
164
        
155
165
    def lock_write(self):
156
166
        raise NotImplementedError('lock_write is abstract')
157
 
 
 
167
        
158
168
    def lock_read(self):
159
169
        raise NotImplementedError('lock_read is abstract')
160
170
 
165
175
        """Return lock mode for the Branch: 'r', 'w' or None"""
166
176
        raise NotImplementedError(self.peek_lock_mode)
167
177
 
168
 
    def get_physical_lock_status(self):
169
 
        raise NotImplementedError('get_physical_lock_status is abstract')
170
 
 
171
178
    def abspath(self, name):
172
179
        """Return absolute filename for something in the branch
173
180
        
176
183
        """
177
184
        raise NotImplementedError('abspath is abstract')
178
185
 
179
 
    def bind(self, other):
180
 
        """Bind the local branch the other branch.
181
 
 
182
 
        :param other: The branch to bind to
183
 
        :type other: Branch
184
 
        """
185
 
        raise errors.UpgradeRequired(self.base)
186
 
 
187
186
    @needs_write_lock
188
187
    def fetch(self, from_branch, last_revision=None, pb=None):
189
188
        """Copy revisions from from_branch into this branch.
197
196
        (copied, failures).
198
197
        """
199
198
        if self.base == from_branch.base:
200
 
            return (0, [])
 
199
            raise Exception("can't fetch from a branch to itself %s, %s" % 
 
200
                            (self.base, to_branch.base))
201
201
        if pb is None:
202
 
            nested_pb = ui.ui_factory.nested_progress_bar()
203
 
            pb = nested_pb
204
 
        else:
205
 
            nested_pb = None
 
202
            pb = bzrlib.ui.ui_factory.progress_bar()
206
203
 
207
204
        from_branch.lock_read()
208
205
        try:
213
210
                    last_revision = from_history[-1]
214
211
                else:
215
212
                    # no history in the source branch
216
 
                    last_revision = revision.NULL_REVISION
 
213
                    last_revision = NULL_REVISION
217
214
            return self.repository.fetch(from_branch.repository,
218
215
                                         revision_id=last_revision,
219
 
                                         pb=nested_pb)
 
216
                                         pb=pb)
220
217
        finally:
221
 
            if nested_pb is not None:
222
 
                nested_pb.finished()
223
218
            from_branch.unlock()
224
219
 
225
 
    def get_bound_location(self):
226
 
        """Return the URL of the branch we are bound to.
227
 
 
228
 
        Older format branches cannot bind, please be sure to use a metadir
229
 
        branch.
230
 
        """
231
 
        return None
232
 
    
233
 
    def get_commit_builder(self, parents, config=None, timestamp=None, 
234
 
                           timezone=None, committer=None, revprops=None, 
235
 
                           revision_id=None):
236
 
        """Obtain a CommitBuilder for this branch.
237
 
        
238
 
        :param parents: Revision ids of the parents of the new revision.
239
 
        :param config: Optional configuration to use.
240
 
        :param timestamp: Optional timestamp recorded for commit.
241
 
        :param timezone: Optional timezone for timestamp.
242
 
        :param committer: Optional committer to set for commit.
243
 
        :param revprops: Optional dictionary of revision properties.
244
 
        :param revision_id: Optional revision id.
245
 
        """
246
 
 
247
 
        if config is None:
248
 
            config = self.get_config()
249
 
        
250
 
        return self.repository.get_commit_builder(self, parents, config, 
251
 
            timestamp, timezone, committer, revprops, revision_id)
252
 
 
253
 
    def get_master_branch(self):
254
 
        """Return the branch we are bound to.
255
 
        
256
 
        :return: Either a Branch, or None
257
 
        """
258
 
        return None
259
 
 
260
 
    def get_revision_delta(self, revno):
261
 
        """Return the delta for one revision.
262
 
 
263
 
        The delta is relative to its mainline predecessor, or the
264
 
        empty tree for revision 1.
265
 
        """
266
 
        assert isinstance(revno, int)
267
 
        rh = self.revision_history()
268
 
        if not (1 <= revno <= len(rh)):
269
 
            raise InvalidRevisionNumber(revno)
270
 
        return self.repository.get_revision_delta(rh[revno-1])
271
 
 
272
220
    def get_root_id(self):
273
221
        """Return the id of this branches root"""
274
222
        raise NotImplementedError('get_root_id is abstract')
295
243
        """
296
244
        return len(self.revision_history())
297
245
 
298
 
    def unbind(self):
299
 
        """Older format branches cannot bind or unbind."""
300
 
        raise errors.UpgradeRequired(self.base)
301
 
 
302
246
    def last_revision(self):
303
247
        """Return last patch hash, or None if no history."""
304
248
        ph = self.revision_history()
307
251
        else:
308
252
            return None
309
253
 
310
 
    def missing_revisions(self, other, stop_revision=None):
 
254
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
311
255
        """Return a list of new revisions that would perfectly fit.
312
256
        
313
257
        If self and other have not diverged, return a list of the revisions
314
258
        present in other, but missing from self.
 
259
 
 
260
        >>> from bzrlib.workingtree import WorkingTree
 
261
        >>> bzrlib.trace.silent = True
 
262
        >>> d1 = bzrdir.ScratchDir()
 
263
        >>> br1 = d1.open_branch()
 
264
        >>> wt1 = d1.open_workingtree()
 
265
        >>> d2 = bzrdir.ScratchDir()
 
266
        >>> br2 = d2.open_branch()
 
267
        >>> wt2 = d2.open_workingtree()
 
268
        >>> br1.missing_revisions(br2)
 
269
        []
 
270
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
 
271
        >>> br1.missing_revisions(br2)
 
272
        [u'REVISION-ID-1']
 
273
        >>> br2.missing_revisions(br1)
 
274
        []
 
275
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
 
276
        >>> br1.missing_revisions(br2)
 
277
        []
 
278
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
 
279
        >>> br1.missing_revisions(br2)
 
280
        [u'REVISION-ID-2A']
 
281
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
 
282
        >>> br1.missing_revisions(br2)
 
283
        Traceback (most recent call last):
 
284
        DivergedBranches: These branches have diverged.  Try merge.
315
285
        """
316
286
        self_history = self.revision_history()
317
287
        self_len = len(self_history)
327
297
        else:
328
298
            assert isinstance(stop_revision, int)
329
299
            if stop_revision > other_len:
330
 
                raise errors.NoSuchRevision(self, stop_revision)
 
300
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
331
301
        return other_history[self_len:stop_revision]
332
302
 
333
303
    def update_revisions(self, other, stop_revision=None):
334
 
        """Pull in new perfect-fit revisions.
335
 
 
336
 
        :param other: Another Branch to pull from
337
 
        :param stop_revision: Updated until the given revision
338
 
        :return: None
339
 
        """
 
304
        """Pull in new perfect-fit revisions."""
340
305
        raise NotImplementedError('update_revisions is abstract')
341
306
 
 
307
    def pullable_revisions(self, other, stop_revision):
 
308
        raise NotImplementedError('pullable_revisions is abstract')
 
309
        
342
310
    def revision_id_to_revno(self, revision_id):
343
311
        """Given a revision id, return its revno"""
344
312
        if revision_id is None:
401
369
        """
402
370
        raise NotImplementedError('get_parent is abstract')
403
371
 
404
 
    def get_submit_branch(self):
405
 
        """Return the submit location of the branch.
406
 
 
407
 
        This is the default location for bundle.  The usual
408
 
        pattern is that the user can override it by specifying a
409
 
        location.
410
 
        """
411
 
        return self.get_config().get_user_option('submit_branch')
412
 
 
413
 
    def set_submit_branch(self, location):
414
 
        """Return the submit location of the branch.
415
 
 
416
 
        This is the default location for bundle.  The usual
417
 
        pattern is that the user can override it by specifying a
418
 
        location.
419
 
        """
420
 
        self.get_config().set_user_option('submit_branch', location)
421
 
 
422
372
    def get_push_location(self):
423
373
        """Return the None or the location to push this branch to."""
424
374
        raise NotImplementedError('get_push_location is abstract')
430
380
    def set_parent(self, url):
431
381
        raise NotImplementedError('set_parent is abstract')
432
382
 
433
 
    @needs_write_lock
434
 
    def update(self):
435
 
        """Synchronise this branch with the master branch if any. 
436
 
 
437
 
        :return: None or the last_revision pivoted out during the update.
438
 
        """
439
 
        return None
440
 
 
441
383
    def check_revno(self, revno):
442
384
        """\
443
385
        Check whether a revno corresponds to any revision.
461
403
        revision_id: if not None, the revision history in the new branch will
462
404
                     be truncated to end with revision_id.
463
405
        """
464
 
        # for API compatibility, until 0.8 releases we provide the old api:
 
406
        # for API compatability, until 0.8 releases we provide the old api:
465
407
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
466
408
        # after 0.8 releases, the *args and **kwargs should be changed:
467
409
        # def clone(self, to_bzrdir, revision_id=None):
469
411
            kwargs.get('revision', None) or
470
412
            kwargs.get('basis_branch', None) or
471
413
            (len(args) and isinstance(args[0], basestring))):
472
 
            # backwards compatibility api:
 
414
            # backwards compatability api:
473
415
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
474
416
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
475
417
            # get basis_branch
541
483
        if parent:
542
484
            destination.set_parent(parent)
543
485
 
544
 
    @needs_read_lock
545
 
    def check(self):
546
 
        """Check consistency of the branch.
547
 
 
548
 
        In particular this checks that revisions given in the revision-history
549
 
        do actually match up in the revision graph, and that they're all 
550
 
        present in the repository.
551
 
        
552
 
        Callers will typically also want to check the repository.
553
 
 
554
 
        :return: A BranchCheckResult.
555
 
        """
556
 
        mainline_parent_id = None
557
 
        for revision_id in self.revision_history():
558
 
            try:
559
 
                revision = self.repository.get_revision(revision_id)
560
 
            except errors.NoSuchRevision, e:
561
 
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
562
 
                            % revision_id)
563
 
            # In general the first entry on the revision history has no parents.
564
 
            # But it's not illegal for it to have parents listed; this can happen
565
 
            # in imports from Arch when the parents weren't reachable.
566
 
            if mainline_parent_id is not None:
567
 
                if mainline_parent_id not in revision.parent_ids:
568
 
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
569
 
                                        "parents of {%s}"
570
 
                                        % (mainline_parent_id, revision_id))
571
 
            mainline_parent_id = revision_id
572
 
        return BranchCheckResult(self)
573
 
 
574
486
 
575
487
class BranchFormat(object):
576
488
    """An encapsulation of the initialization and open routines for a format.
606
518
        except NoSuchFile:
607
519
            raise NotBranchError(path=transport.base)
608
520
        except KeyError:
609
 
            raise errors.UnknownFormatError(format=format_string)
 
521
            raise errors.UnknownFormatError(format_string)
610
522
 
611
523
    @classmethod
612
524
    def get_default_format(klass):
617
529
        """Return the ASCII format string that identifies this format."""
618
530
        raise NotImplementedError(self.get_format_string)
619
531
 
620
 
    def get_format_description(self):
621
 
        """Return the short format description for this format."""
622
 
        raise NotImplementedError(self.get_format_string)
623
 
 
624
532
    def initialize(self, a_bzrdir):
625
533
        """Create a branch of this format in a_bzrdir."""
626
 
        raise NotImplementedError(self.initialize)
 
534
        raise NotImplementedError(self.initialized)
627
535
 
628
536
    def is_supported(self):
629
537
        """Is this format supported?
655
563
        assert klass._formats[format.get_format_string()] is format
656
564
        del klass._formats[format.get_format_string()]
657
565
 
658
 
    def __str__(self):
659
 
        return self.get_format_string().rstrip()
660
 
 
661
566
 
662
567
class BzrBranchFormat4(BranchFormat):
663
568
    """Bzr branch format 4.
667
572
     - a branch-lock lock file [ to be shared with the bzrdir ]
668
573
    """
669
574
 
670
 
    def get_format_description(self):
671
 
        """See BranchFormat.get_format_description()."""
672
 
        return "Branch format 4"
673
 
 
674
575
    def initialize(self, a_bzrdir):
675
576
        """Create a branch of this format in a_bzrdir."""
676
577
        mutter('creating branch in %s', a_bzrdir.transport.base)
678
579
        utf8_files = [('revision-history', ''),
679
580
                      ('branch-name', ''),
680
581
                      ]
681
 
        control_files = LockableFiles(branch_transport, 'branch-lock',
682
 
                                      TransportLock)
683
 
        control_files.create_lock()
 
582
        control_files = LockableFiles(branch_transport, 'branch-lock')
684
583
        control_files.lock_write()
685
584
        try:
686
585
            for file, content in utf8_files:
707
606
                         a_bzrdir=a_bzrdir,
708
607
                         _repository=a_bzrdir.open_repository())
709
608
 
710
 
    def __str__(self):
711
 
        return "Bazaar-NG branch format 4"
712
 
 
713
609
 
714
610
class BzrBranchFormat5(BranchFormat):
715
611
    """Bzr branch format 5.
717
613
    This format has:
718
614
     - a revision-history file.
719
615
     - a format string
720
 
     - a lock dir guarding the branch itself
721
 
     - all of this stored in a branch/ subdirectory
 
616
     - a lock file.
722
617
     - works with shared repositories.
723
 
 
724
 
    This format is new in bzr 0.8.
725
618
    """
726
619
 
727
620
    def get_format_string(self):
728
621
        """See BranchFormat.get_format_string()."""
729
622
        return "Bazaar-NG branch format 5\n"
730
 
 
731
 
    def get_format_description(self):
732
 
        """See BranchFormat.get_format_description()."""
733
 
        return "Branch format 5"
734
623
        
735
624
    def initialize(self, a_bzrdir):
736
625
        """Create a branch of this format in a_bzrdir."""
737
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
626
        mutter('creating branch in %s', a_bzrdir.transport.base)
738
627
        branch_transport = a_bzrdir.get_branch_transport(self)
 
628
 
739
629
        utf8_files = [('revision-history', ''),
740
630
                      ('branch-name', ''),
741
631
                      ]
742
 
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
743
 
        control_files.create_lock()
 
632
        lock_file = 'lock'
 
633
        branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
 
634
        control_files = LockableFiles(branch_transport, 'lock')
744
635
        control_files.lock_write()
745
636
        control_files.put_utf8('format', self.get_format_string())
746
637
        try:
764
655
            format = BranchFormat.find_format(a_bzrdir)
765
656
            assert format.__class__ == self.__class__
766
657
        transport = a_bzrdir.get_branch_transport(None)
767
 
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
768
 
        return BzrBranch5(_format=self,
769
 
                          _control_files=control_files,
770
 
                          a_bzrdir=a_bzrdir,
771
 
                          _repository=a_bzrdir.find_repository())
772
 
 
773
 
    def __str__(self):
774
 
        return "Bazaar-NG Metadir branch format 5"
 
658
        control_files = LockableFiles(transport, 'lock')
 
659
        return BzrBranch(_format=self,
 
660
                         _control_files=control_files,
 
661
                         a_bzrdir=a_bzrdir,
 
662
                         _repository=a_bzrdir.find_repository())
775
663
 
776
664
 
777
665
class BranchReferenceFormat(BranchFormat):
788
676
    def get_format_string(self):
789
677
        """See BranchFormat.get_format_string()."""
790
678
        return "Bazaar-NG Branch Reference Format 1\n"
791
 
 
792
 
    def get_format_description(self):
793
 
        """See BranchFormat.get_format_description()."""
794
 
        return "Checkout reference format 1"
795
679
        
796
680
    def initialize(self, a_bzrdir, target_branch=None):
797
681
        """Create a branch of this format in a_bzrdir."""
860
744
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
861
745
    it's writable, and can be accessed via the normal filesystem API.
862
746
    """
 
747
    # We actually expect this class to be somewhat short-lived; part of its
 
748
    # purpose is to try to isolate what bits of the branch logic are tied to
 
749
    # filesystem access, so that in a later step, we can extricate them to
 
750
    # a separarte ("storage") class.
 
751
    _inventory_weave = None
863
752
    
 
753
    # Map some sort of prefix into a namespace
 
754
    # stuff like "revno:10", "revid:", etc.
 
755
    # This should match a prefix with a function which accepts
 
756
    REVISION_NAMESPACES = {}
 
757
 
864
758
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
865
759
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
866
760
                 _control_files=None, a_bzrdir=None, _repository=None):
885
779
        self._base = self._transport.base
886
780
        self._format = _format
887
781
        if _control_files is None:
888
 
            raise ValueError('BzrBranch _control_files is None')
 
782
            raise BzrBadParameterMissing('_control_files')
889
783
        self.control_files = _control_files
890
784
        if deprecated_passed(init):
891
785
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
906
800
                 stacklevel=2)
907
801
            if (not relax_version_check
908
802
                and not self._format.is_supported()):
909
 
                raise errors.UnsupportedFormatError(format=fmt)
 
803
                raise errors.UnsupportedFormatError(
 
804
                        'sorry, branch format %r not supported' % fmt,
 
805
                        ['use a different bzr version',
 
806
                         'or remove the .bzr directory'
 
807
                         ' and "bzr init" again'])
910
808
        if deprecated_passed(transport):
911
809
            warn("BzrBranch.__init__(transport=XXX...): The transport "
912
810
                 "parameter is deprecated as of bzr 0.8. "
930
828
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
931
829
        if hasattr(self, 'cache_root') and self.cache_root is not None:
932
830
            try:
933
 
                osutils.rmtree(self.cache_root)
 
831
                shutil.rmtree(self.cache_root)
934
832
            except:
935
833
                pass
936
834
            self.cache_root = None
979
877
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
980
878
        """
981
879
        if format is None:
982
 
            format = BranchFormat.find_format(self.bzrdir)
 
880
            format = BzrBranchFormat.find_format(self.bzrdir)
983
881
        self._format = format
984
882
        mutter("got branch format %s", self._format)
985
883
 
989
887
        tree = self.repository.revision_tree(self.last_revision())
990
888
        return tree.inventory.root.file_id
991
889
 
992
 
    def is_locked(self):
993
 
        return self.control_files.is_locked()
994
 
 
995
890
    def lock_write(self):
 
891
        # TODO: test for failed two phase locks. This is known broken.
 
892
        self.control_files.lock_write()
996
893
        self.repository.lock_write()
997
 
        try:
998
 
            self.control_files.lock_write()
999
 
        except:
1000
 
            self.repository.unlock()
1001
 
            raise
1002
894
 
1003
895
    def lock_read(self):
 
896
        # TODO: test for failed two phase locks. This is known broken.
 
897
        self.control_files.lock_read()
1004
898
        self.repository.lock_read()
1005
 
        try:
1006
 
            self.control_files.lock_read()
1007
 
        except:
1008
 
            self.repository.unlock()
1009
 
            raise
1010
899
 
1011
900
    def unlock(self):
1012
901
        # TODO: test for failed two phase locks. This is known broken.
1013
 
        try:
1014
 
            self.control_files.unlock()
1015
 
        finally:
1016
 
            self.repository.unlock()
1017
 
        
 
902
        self.repository.unlock()
 
903
        self.control_files.unlock()
 
904
 
1018
905
    def peek_lock_mode(self):
1019
906
        if self.control_files._lock_count == 0:
1020
907
            return None
1021
908
        else:
1022
909
            return self.control_files._lock_mode
1023
910
 
1024
 
    def get_physical_lock_status(self):
1025
 
        return self.control_files.get_physical_lock_status()
1026
 
 
1027
911
    @needs_read_lock
1028
912
    def print_file(self, file, revision_id):
1029
913
        """See Branch.print_file."""
1043
927
        """See Branch.set_revision_history."""
1044
928
        self.control_files.put_utf8(
1045
929
            'revision-history', '\n'.join(rev_history))
1046
 
        transaction = self.get_transaction()
1047
 
        history = transaction.map.find_revision_history()
1048
 
        if history is not None:
1049
 
            # update the revision history in the identity map.
1050
 
            history[:] = list(rev_history)
1051
 
            # this call is disabled because revision_history is 
1052
 
            # not really an object yet, and the transaction is for objects.
1053
 
            # transaction.register_dirty(history)
 
930
 
 
931
    def get_revision_delta(self, revno):
 
932
        """Return the delta for one revision.
 
933
 
 
934
        The delta is relative to its mainline predecessor, or the
 
935
        empty tree for revision 1.
 
936
        """
 
937
        assert isinstance(revno, int)
 
938
        rh = self.revision_history()
 
939
        if not (1 <= revno <= len(rh)):
 
940
            raise InvalidRevisionNumber(revno)
 
941
 
 
942
        # revno is 1-based; list is 0-based
 
943
 
 
944
        new_tree = self.repository.revision_tree(rh[revno-1])
 
945
        if revno == 1:
 
946
            old_tree = EmptyTree()
1054
947
        else:
1055
 
            transaction.map.add_revision_history(rev_history)
1056
 
            # this call is disabled because revision_history is 
1057
 
            # not really an object yet, and the transaction is for objects.
1058
 
            # transaction.register_clean(history)
 
948
            old_tree = self.repository.revision_tree(rh[revno-2])
 
949
        return compare_trees(old_tree, new_tree)
1059
950
 
1060
951
    @needs_read_lock
1061
952
    def revision_history(self):
1062
953
        """See Branch.revision_history."""
 
954
        # FIXME are transactions bound to control files ? RBC 20051121
1063
955
        transaction = self.get_transaction()
1064
956
        history = transaction.map.find_revision_history()
1065
957
        if history is not None:
1073
965
        # transaction.register_clean(history, precious=True)
1074
966
        return list(history)
1075
967
 
1076
 
    @needs_write_lock
1077
 
    def generate_revision_history(self, revision_id, last_rev=None, 
1078
 
        other_branch=None):
1079
 
        """Create a new revision history that will finish with revision_id.
1080
 
        
1081
 
        :param revision_id: the new tip to use.
1082
 
        :param last_rev: The previous last_revision. If not None, then this
1083
 
            must be a ancestory of revision_id, or DivergedBranches is raised.
1084
 
        :param other_branch: The other branch that DivergedBranches should
1085
 
            raise with respect to.
1086
 
        """
1087
 
        # stop_revision must be a descendant of last_revision
1088
 
        stop_graph = self.repository.get_revision_graph(revision_id)
1089
 
        if last_rev is not None and last_rev not in stop_graph:
1090
 
            # our previous tip is not merged into stop_revision
1091
 
            raise errors.DivergedBranches(self, other_branch)
1092
 
        # make a new revision history from the graph
1093
 
        current_rev_id = revision_id
1094
 
        new_history = []
1095
 
        while current_rev_id not in (None, revision.NULL_REVISION):
1096
 
            new_history.append(current_rev_id)
1097
 
            current_rev_id_parents = stop_graph[current_rev_id]
1098
 
            try:
1099
 
                current_rev_id = current_rev_id_parents[0]
1100
 
            except IndexError:
1101
 
                current_rev_id = None
1102
 
        new_history.reverse()
1103
 
        self.set_revision_history(new_history)
1104
 
 
1105
 
    @needs_write_lock
1106
968
    def update_revisions(self, other, stop_revision=None):
1107
969
        """See Branch.update_revisions."""
1108
 
        other.lock_read()
 
970
        if stop_revision is None:
 
971
            stop_revision = other.last_revision()
 
972
        ### Should this be checking is_ancestor instead of revision_history?
 
973
        if (stop_revision is not None and 
 
974
            stop_revision in self.revision_history()):
 
975
            return
 
976
        self.fetch(other, stop_revision)
 
977
        pullable_revs = self.pullable_revisions(other, stop_revision)
 
978
        if len(pullable_revs) > 0:
 
979
            self.append_revision(*pullable_revs)
 
980
 
 
981
    def pullable_revisions(self, other, stop_revision):
 
982
        """See Branch.pullable_revisions."""
 
983
        other_revno = other.revision_id_to_revno(stop_revision)
1109
984
        try:
1110
 
            if stop_revision is None:
1111
 
                stop_revision = other.last_revision()
1112
 
                if stop_revision is None:
1113
 
                    # if there are no commits, we're done.
1114
 
                    return
1115
 
            # whats the current last revision, before we fetch [and change it
1116
 
            # possibly]
1117
 
            last_rev = self.last_revision()
1118
 
            # we fetch here regardless of whether we need to so that we pickup
1119
 
            # filled in ghosts.
1120
 
            self.fetch(other, stop_revision)
1121
 
            my_ancestry = self.repository.get_ancestry(last_rev)
1122
 
            if stop_revision in my_ancestry:
1123
 
                # last_revision is a descendant of stop_revision
1124
 
                return
1125
 
            self.generate_revision_history(stop_revision, last_rev=last_rev,
1126
 
                other_branch=other)
1127
 
        finally:
1128
 
            other.unlock()
1129
 
 
 
985
            return self.missing_revisions(other, other_revno)
 
986
        except DivergedBranches, e:
 
987
            try:
 
988
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
989
                                                          stop_revision, 
 
990
                                                          self.repository)
 
991
                assert self.last_revision() not in pullable_revs
 
992
                return pullable_revs
 
993
            except bzrlib.errors.NotAncestor:
 
994
                if is_ancestor(self.last_revision(), stop_revision, self):
 
995
                    return []
 
996
                else:
 
997
                    raise e
 
998
        
1130
999
    def basis_tree(self):
1131
1000
        """See Branch.basis_tree."""
1132
1001
        return self.repository.revision_tree(self.last_revision())
1134
1003
    @deprecated_method(zero_eight)
1135
1004
    def working_tree(self):
1136
1005
        """Create a Working tree object for this branch."""
1137
 
 
 
1006
        from bzrlib.workingtree import WorkingTree
1138
1007
        from bzrlib.transport.local import LocalTransport
1139
1008
        if (self.base.find('://') != -1 or 
1140
1009
            not isinstance(self._transport, LocalTransport)):
1161
1030
 
1162
1031
    def get_parent(self):
1163
1032
        """See Branch.get_parent."""
1164
 
 
 
1033
        import errno
1165
1034
        _locs = ['parent', 'pull', 'x-pull']
1166
 
        assert self.base[-1] == '/'
1167
1035
        for l in _locs:
1168
1036
            try:
1169
 
                parent = self.control_files.get(l).read().strip('\n')
 
1037
                return self.control_files.get_utf8(l).read().strip('\n')
1170
1038
            except NoSuchFile:
1171
 
                continue
1172
 
            # This is an old-format absolute path to a local branch
1173
 
            # turn it into a url
1174
 
            if parent.startswith('/'):
1175
 
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
1176
 
            return urlutils.join(self.base[:-1], parent)
 
1039
                pass
1177
1040
        return None
1178
1041
 
1179
1042
    def get_push_location(self):
1180
1043
        """See Branch.get_push_location."""
1181
 
        push_loc = self.get_config().get_user_option('push_location')
 
1044
        config = bzrlib.config.BranchConfig(self)
 
1045
        push_loc = config.get_user_option('push_location')
1182
1046
        return push_loc
1183
1047
 
1184
1048
    def set_push_location(self, location):
1185
1049
        """See Branch.set_push_location."""
1186
 
        self.get_config().set_user_option('push_location', location, 
1187
 
                                          local=True)
 
1050
        config = bzrlib.config.LocationConfig(self.base)
 
1051
        config.set_user_option('push_location', location)
1188
1052
 
1189
1053
    @needs_write_lock
1190
1054
    def set_parent(self, url):
1194
1058
        # FIXUP this and get_parent in a future branch format bump:
1195
1059
        # read and rewrite the file, and have the new format code read
1196
1060
        # using .get not .get_utf8. RBC 20060125
1197
 
        if url is None:
1198
 
            self.control_files._transport.delete('parent')
1199
 
        else:
1200
 
            if isinstance(url, unicode):
1201
 
                try: 
1202
 
                    url = url.encode('ascii')
1203
 
                except UnicodeEncodeError:
1204
 
                    raise bzrlib.errors.InvalidURL(url,
1205
 
                        "Urls must be 7-bit ascii, "
1206
 
                        "use bzrlib.urlutils.escape")
1207
 
                    
1208
 
            url = urlutils.relative_url(self.base, url)
1209
 
            self.control_files.put('parent', url + '\n')
 
1061
        self.control_files.put_utf8('parent', url + '\n')
1210
1062
 
1211
 
    @deprecated_function(zero_nine)
1212
1063
    def tree_config(self):
1213
 
        """DEPRECATED; call get_config instead.  
1214
 
        TreeConfig has become part of BranchConfig."""
1215
1064
        return TreeConfig(self)
1216
1065
 
1217
 
 
1218
 
class BzrBranch5(BzrBranch):
1219
 
    """A format 5 branch. This supports new features over plan branches.
1220
 
 
1221
 
    It has support for a master_branch which is the data for bound branches.
1222
 
    """
1223
 
 
1224
 
    def __init__(self,
1225
 
                 _format,
1226
 
                 _control_files,
1227
 
                 a_bzrdir,
1228
 
                 _repository):
1229
 
        super(BzrBranch5, self).__init__(_format=_format,
1230
 
                                         _control_files=_control_files,
1231
 
                                         a_bzrdir=a_bzrdir,
1232
 
                                         _repository=_repository)
1233
 
        
1234
 
    @needs_write_lock
1235
 
    def pull(self, source, overwrite=False, stop_revision=None):
1236
 
        """Updates branch.pull to be bound branch aware."""
1237
 
        bound_location = self.get_bound_location()
1238
 
        if source.base != bound_location:
1239
 
            # not pulling from master, so we need to update master.
1240
 
            master_branch = self.get_master_branch()
1241
 
            if master_branch:
1242
 
                master_branch.pull(source)
1243
 
                source = master_branch
1244
 
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1245
 
 
1246
 
    def get_bound_location(self):
 
1066
    def _get_truncated_history(self, revision_id):
 
1067
        history = self.revision_history()
 
1068
        if revision_id is None:
 
1069
            return history
1247
1070
        try:
1248
 
            return self.control_files.get_utf8('bound').read()[:-1]
1249
 
        except errors.NoSuchFile:
1250
 
            return None
 
1071
            idx = history.index(revision_id)
 
1072
        except ValueError:
 
1073
            raise InvalidRevisionId(revision_id=revision, branch=self)
 
1074
        return history[:idx+1]
1251
1075
 
1252
1076
    @needs_read_lock
1253
 
    def get_master_branch(self):
1254
 
        """Return the branch we are bound to.
1255
 
        
1256
 
        :return: Either a Branch, or None
1257
 
 
1258
 
        This could memoise the branch, but if thats done
1259
 
        it must be revalidated on each new lock.
1260
 
        So for now we just don't memoise it.
1261
 
        # RBC 20060304 review this decision.
1262
 
        """
1263
 
        bound_loc = self.get_bound_location()
1264
 
        if not bound_loc:
1265
 
            return None
1266
 
        try:
1267
 
            return Branch.open(bound_loc)
1268
 
        except (errors.NotBranchError, errors.ConnectionError), e:
1269
 
            raise errors.BoundBranchConnectionFailure(
1270
 
                    self, bound_loc, e)
1271
 
 
1272
 
    @needs_write_lock
1273
 
    def set_bound_location(self, location):
1274
 
        """Set the target where this branch is bound to.
1275
 
 
1276
 
        :param location: URL to the target branch
1277
 
        """
1278
 
        if location:
1279
 
            self.control_files.put_utf8('bound', location+'\n')
1280
 
        else:
1281
 
            try:
1282
 
                self.control_files._transport.delete('bound')
1283
 
            except NoSuchFile:
1284
 
                return False
1285
 
            return True
1286
 
 
1287
 
    @needs_write_lock
1288
 
    def bind(self, other):
1289
 
        """Bind the local branch the other branch.
1290
 
 
1291
 
        :param other: The branch to bind to
1292
 
        :type other: Branch
1293
 
        """
1294
 
        # TODO: jam 20051230 Consider checking if the target is bound
1295
 
        #       It is debatable whether you should be able to bind to
1296
 
        #       a branch which is itself bound.
1297
 
        #       Committing is obviously forbidden,
1298
 
        #       but binding itself may not be.
1299
 
        #       Since we *have* to check at commit time, we don't
1300
 
        #       *need* to check here
1301
 
        self.pull(other)
1302
 
 
1303
 
        # we are now equal to or a suffix of other.
1304
 
 
1305
 
        # Since we have 'pulled' from the remote location,
1306
 
        # now we should try to pull in the opposite direction
1307
 
        # in case the local tree has more revisions than the
1308
 
        # remote one.
1309
 
        # There may be a different check you could do here
1310
 
        # rather than actually trying to install revisions remotely.
1311
 
        # TODO: capture an exception which indicates the remote branch
1312
 
        #       is not writable. 
1313
 
        #       If it is up-to-date, this probably should not be a failure
1314
 
        
1315
 
        # lock other for write so the revision-history syncing cannot race
1316
 
        other.lock_write()
1317
 
        try:
1318
 
            other.pull(self)
1319
 
            # if this does not error, other now has the same last rev we do
1320
 
            # it can only error if the pull from other was concurrent with
1321
 
            # a commit to other from someone else.
1322
 
 
1323
 
            # until we ditch revision-history, we need to sync them up:
1324
 
            self.set_revision_history(other.revision_history())
1325
 
            # now other and self are up to date with each other and have the
1326
 
            # same revision-history.
1327
 
        finally:
1328
 
            other.unlock()
1329
 
 
1330
 
        self.set_bound_location(other.base)
1331
 
 
1332
 
    @needs_write_lock
1333
 
    def unbind(self):
1334
 
        """If bound, unbind"""
1335
 
        return self.set_bound_location(None)
1336
 
 
1337
 
    @needs_write_lock
1338
 
    def update(self):
1339
 
        """Synchronise this branch with the master branch if any. 
1340
 
 
1341
 
        :return: None or the last_revision that was pivoted out during the
1342
 
                 update.
1343
 
        """
1344
 
        master = self.get_master_branch()
1345
 
        if master is not None:
1346
 
            old_tip = self.last_revision()
1347
 
            self.pull(master, overwrite=True)
1348
 
            if old_tip in self.repository.get_ancestry(self.last_revision()):
1349
 
                return None
1350
 
            return old_tip
1351
 
        return None
 
1077
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
 
1078
        # prevent leakage
 
1079
        from bzrlib.workingtree import WorkingTree
 
1080
        assert isinstance(to_location, basestring)
 
1081
        if basis_branch is not None:
 
1082
            note("basis_branch is not supported for fast weave copy yet.")
 
1083
 
 
1084
        history = self._get_truncated_history(revision)
 
1085
        if not bzrlib.osutils.lexists(to_location):
 
1086
            os.mkdir(to_location)
 
1087
        bzrdir_to = self.bzrdir._format.initialize(to_location)
 
1088
        self.repository.clone(bzrdir_to)
 
1089
        branch_to = bzrdir_to.create_branch()
 
1090
        mutter("copy branch from %s to %s", self, branch_to)
 
1091
 
 
1092
        # FIXME duplicate code with base .clone().
 
1093
        # .. would template method be useful here?  RBC 20051207
 
1094
        branch_to.set_parent(self.base)
 
1095
        branch_to.append_revision(*history)
 
1096
        WorkingTree.create(branch_to, branch_to.base)
 
1097
        mutter("copied")
 
1098
        return branch_to
1352
1099
 
1353
1100
 
1354
1101
class BranchTestProviderAdapter(object):
1381
1128
        return result
1382
1129
 
1383
1130
 
1384
 
class BranchCheckResult(object):
1385
 
    """Results of checking branch consistency.
1386
 
 
1387
 
    :see: Branch.check
1388
 
    """
1389
 
 
1390
 
    def __init__(self, branch):
1391
 
        self.branch = branch
1392
 
 
1393
 
    def report_results(self, verbose):
1394
 
        """Report the check results via trace.note.
1395
 
        
1396
 
        :param verbose: Requests more detailed display of what was checked,
1397
 
            if any.
1398
 
        """
1399
 
        note('checked branch %s format %s',
1400
 
             self.branch.base,
1401
 
             self.branch._format)
1402
 
 
1403
 
 
1404
1131
######################################################################
1405
1132
# predicates
1406
1133
 
1407
1134
 
1408
1135
@deprecated_function(zero_eight)
 
1136
def ScratchBranch(*args, **kwargs):
 
1137
    """See bzrlib.bzrdir.ScratchDir."""
 
1138
    d = ScratchDir(*args, **kwargs)
 
1139
    return d.open_branch()
 
1140
 
 
1141
 
 
1142
@deprecated_function(zero_eight)
1409
1143
def is_control_file(*args, **kwargs):
1410
1144
    """See bzrlib.workingtree.is_control_file."""
1411
1145
    return bzrlib.workingtree.is_control_file(*args, **kwargs)