~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2006-03-06 04:43:59 UTC
  • mto: This revision was merged to the branch mainline in revision 1593.
  • Revision ID: mbp@sourcefrog.net-20060306044359-f977728599659b6d
Add tests that MetaDir repositories use LockDirs

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
23
23
import sys
24
24
from unittest import TestSuite
25
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
 
26
34
 
27
35
import bzrlib
28
36
import bzrlib.bzrdir as bzrdir
39
47
                           NoWorkingTree)
40
48
import bzrlib.inventory as inventory
41
49
from bzrlib.inventory import Inventory
42
 
from bzrlib.lockable_files import LockableFiles, TransportLock
43
 
from bzrlib.lockdir import LockDir
 
50
from bzrlib.lockable_files import LockableFiles
44
51
from bzrlib.osutils import (isdir, quotefn,
45
52
                            rename, splitpath, sha_file,
46
53
                            file_kind, abspath, normpath, pathjoin,
47
54
                            safe_unicode,
48
 
                            rmtree,
49
55
                            )
50
56
from bzrlib.textui import show_status
51
57
from bzrlib.trace import mutter, note
52
58
from bzrlib.tree import EmptyTree, RevisionTree
53
59
from bzrlib.repository import Repository
54
60
from bzrlib.revision import (
 
61
                             get_intervening_revisions,
55
62
                             is_ancestor,
56
63
                             NULL_REVISION,
57
64
                             Revision,
95
102
    def __init__(self, *ignored, **ignored_too):
96
103
        raise NotImplementedError('The Branch class is abstract')
97
104
 
98
 
    def break_lock(self):
99
 
        """Break a lock if one is present from another instance.
100
 
 
101
 
        Uses the ui factory to ask for confirmation if the lock may be from
102
 
        an active process.
103
 
 
104
 
        This will probe the repository for its lock as well.
105
 
        """
106
 
        self.control_files.break_lock()
107
 
        self.repository.break_lock()
108
 
        master = self.get_master_branch()
109
 
        if master is not None:
110
 
            master.break_lock()
111
 
 
112
105
    @staticmethod
113
106
    @deprecated_method(zero_eight)
114
107
    def open_downlevel(base):
168
161
        assert cfg.get_option("nickname") == nick
169
162
 
170
163
    nick = property(_get_nick, _set_nick)
171
 
 
172
 
    def is_locked(self):
173
 
        raise NotImplementedError('is_locked is abstract')
174
 
 
 
164
        
175
165
    def lock_write(self):
176
166
        raise NotImplementedError('lock_write is abstract')
177
 
 
 
167
        
178
168
    def lock_read(self):
179
169
        raise NotImplementedError('lock_read is abstract')
180
170
 
185
175
        """Return lock mode for the Branch: 'r', 'w' or None"""
186
176
        raise NotImplementedError(self.peek_lock_mode)
187
177
 
188
 
    def get_physical_lock_status(self):
189
 
        raise NotImplementedError('get_physical_lock_status is abstract')
190
 
 
191
178
    def abspath(self, name):
192
179
        """Return absolute filename for something in the branch
193
180
        
196
183
        """
197
184
        raise NotImplementedError('abspath is abstract')
198
185
 
199
 
    def bind(self, other):
200
 
        """Bind the local branch the other branch.
201
 
 
202
 
        :param other: The branch to bind to
203
 
        :type other: Branch
204
 
        """
205
 
        raise errors.UpgradeRequired(self.base)
206
 
 
207
186
    @needs_write_lock
208
187
    def fetch(self, from_branch, last_revision=None, pb=None):
209
188
        """Copy revisions from from_branch into this branch.
217
196
        (copied, failures).
218
197
        """
219
198
        if self.base == from_branch.base:
220
 
            return (0, [])
 
199
            raise Exception("can't fetch from a branch to itself %s, %s" % 
 
200
                            (self.base, to_branch.base))
221
201
        if pb is None:
222
 
            nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
223
 
            pb = nested_pb
224
 
        else:
225
 
            nested_pb = None
 
202
            pb = bzrlib.ui.ui_factory.progress_bar()
226
203
 
227
204
        from_branch.lock_read()
228
205
        try:
236
213
                    last_revision = NULL_REVISION
237
214
            return self.repository.fetch(from_branch.repository,
238
215
                                         revision_id=last_revision,
239
 
                                         pb=nested_pb)
 
216
                                         pb=pb)
240
217
        finally:
241
 
            if nested_pb is not None:
242
 
                nested_pb.finished()
243
218
            from_branch.unlock()
244
219
 
245
 
    def get_bound_location(self):
246
 
        """Return the URL of the branch we are bound to.
247
 
 
248
 
        Older format branches cannot bind, please be sure to use a metadir
249
 
        branch.
250
 
        """
251
 
        return None
252
 
 
253
 
    def get_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
220
    def get_root_id(self):
261
221
        """Return the id of this branches root"""
262
222
        raise NotImplementedError('get_root_id is abstract')
283
243
        """
284
244
        return len(self.revision_history())
285
245
 
286
 
    def unbind(self):
287
 
        """Older format branches cannot bind or unbind."""
288
 
        raise errors.UpgradeRequired(self.base)
289
 
 
290
246
    def last_revision(self):
291
247
        """Return last patch hash, or None if no history."""
292
248
        ph = self.revision_history()
295
251
        else:
296
252
            return None
297
253
 
298
 
    def missing_revisions(self, other, stop_revision=None):
 
254
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
299
255
        """Return a list of new revisions that would perfectly fit.
300
256
        
301
257
        If self and other have not diverged, return a list of the revisions
345
301
        return other_history[self_len:stop_revision]
346
302
 
347
303
    def update_revisions(self, other, stop_revision=None):
348
 
        """Pull in new perfect-fit revisions.
349
 
 
350
 
        :param other: Another Branch to pull from
351
 
        :param stop_revision: Updated until the given revision
352
 
        :return: None
353
 
        """
 
304
        """Pull in new perfect-fit revisions."""
354
305
        raise NotImplementedError('update_revisions is abstract')
355
306
 
 
307
    def pullable_revisions(self, other, stop_revision):
 
308
        raise NotImplementedError('pullable_revisions is abstract')
 
309
        
356
310
    def revision_id_to_revno(self, revision_id):
357
311
        """Given a revision id, return its revno"""
358
312
        if revision_id is None:
426
380
    def set_parent(self, url):
427
381
        raise NotImplementedError('set_parent is abstract')
428
382
 
429
 
    @needs_write_lock
430
 
    def update(self):
431
 
        """Synchronise this branch with the master branch if any. 
432
 
 
433
 
        :return: None or the last_revision pivoted out during the update.
434
 
        """
435
 
        return None
436
 
 
437
383
    def check_revno(self, revno):
438
384
        """\
439
385
        Check whether a revno corresponds to any revision.
583
529
        """Return the ASCII format string that identifies this format."""
584
530
        raise NotImplementedError(self.get_format_string)
585
531
 
586
 
    def get_format_description(self):
587
 
        """Return the short format description for this format."""
588
 
        raise NotImplementedError(self.get_format_string)
589
 
 
590
532
    def initialize(self, a_bzrdir):
591
533
        """Create a branch of this format in a_bzrdir."""
592
534
        raise NotImplementedError(self.initialized)
621
563
        assert klass._formats[format.get_format_string()] is format
622
564
        del klass._formats[format.get_format_string()]
623
565
 
624
 
    def __str__(self):
625
 
        return self.get_format_string().rstrip()
626
 
 
627
566
 
628
567
class BzrBranchFormat4(BranchFormat):
629
568
    """Bzr branch format 4.
633
572
     - a branch-lock lock file [ to be shared with the bzrdir ]
634
573
    """
635
574
 
636
 
    def get_format_description(self):
637
 
        """See BranchFormat.get_format_description()."""
638
 
        return "Branch format 4"
639
 
 
640
575
    def initialize(self, a_bzrdir):
641
576
        """Create a branch of this format in a_bzrdir."""
642
577
        mutter('creating branch in %s', a_bzrdir.transport.base)
644
579
        utf8_files = [('revision-history', ''),
645
580
                      ('branch-name', ''),
646
581
                      ]
647
 
        control_files = LockableFiles(branch_transport, 'branch-lock',
648
 
                                      TransportLock)
649
 
        control_files.create_lock()
 
582
        control_files = LockableFiles(branch_transport, 'branch-lock')
650
583
        control_files.lock_write()
651
584
        try:
652
585
            for file, content in utf8_files:
673
606
                         a_bzrdir=a_bzrdir,
674
607
                         _repository=a_bzrdir.open_repository())
675
608
 
676
 
    def __str__(self):
677
 
        return "Bazaar-NG branch format 4"
678
 
 
679
609
 
680
610
class BzrBranchFormat5(BranchFormat):
681
611
    """Bzr branch format 5.
683
613
    This format has:
684
614
     - a revision-history file.
685
615
     - a format string
686
 
     - a lock dir guarding the branch itself
687
 
     - all of this stored in a branch/ subdirectory
 
616
     - a lock file.
688
617
     - works with shared repositories.
689
 
 
690
 
    This format is new in bzr 0.8.
691
618
    """
692
619
 
693
620
    def get_format_string(self):
694
621
        """See BranchFormat.get_format_string()."""
695
622
        return "Bazaar-NG branch format 5\n"
696
 
 
697
 
    def get_format_description(self):
698
 
        """See BranchFormat.get_format_description()."""
699
 
        return "Branch format 5"
700
623
        
701
624
    def initialize(self, a_bzrdir):
702
625
        """Create a branch of this format in a_bzrdir."""
703
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
626
        mutter('creating branch in %s', a_bzrdir.transport.base)
704
627
        branch_transport = a_bzrdir.get_branch_transport(self)
 
628
 
705
629
        utf8_files = [('revision-history', ''),
706
630
                      ('branch-name', ''),
707
631
                      ]
708
 
        control_files = LockableFiles(branch_transport, 'lock', LockDir)
709
 
        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')
710
635
        control_files.lock_write()
711
636
        control_files.put_utf8('format', self.get_format_string())
712
637
        try:
730
655
            format = BranchFormat.find_format(a_bzrdir)
731
656
            assert format.__class__ == self.__class__
732
657
        transport = a_bzrdir.get_branch_transport(None)
733
 
        control_files = LockableFiles(transport, 'lock', LockDir)
734
 
        return BzrBranch5(_format=self,
735
 
                          _control_files=control_files,
736
 
                          a_bzrdir=a_bzrdir,
737
 
                          _repository=a_bzrdir.find_repository())
738
 
 
739
 
    def __str__(self):
740
 
        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())
741
663
 
742
664
 
743
665
class BranchReferenceFormat(BranchFormat):
754
676
    def get_format_string(self):
755
677
        """See BranchFormat.get_format_string()."""
756
678
        return "Bazaar-NG Branch Reference Format 1\n"
757
 
 
758
 
    def get_format_description(self):
759
 
        """See BranchFormat.get_format_description()."""
760
 
        return "Checkout reference format 1"
761
679
        
762
680
    def initialize(self, a_bzrdir, target_branch=None):
763
681
        """Create a branch of this format in a_bzrdir."""
826
744
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
827
745
    it's writable, and can be accessed via the normal filesystem API.
828
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
829
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
 
830
758
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
831
759
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
832
760
                 _control_files=None, a_bzrdir=None, _repository=None):
900
828
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
901
829
        if hasattr(self, 'cache_root') and self.cache_root is not None:
902
830
            try:
903
 
                rmtree(self.cache_root)
 
831
                shutil.rmtree(self.cache_root)
904
832
            except:
905
833
                pass
906
834
            self.cache_root = None
959
887
        tree = self.repository.revision_tree(self.last_revision())
960
888
        return tree.inventory.root.file_id
961
889
 
962
 
    def is_locked(self):
963
 
        return self.control_files.is_locked()
964
 
 
965
890
    def lock_write(self):
966
891
        # TODO: test for failed two phase locks. This is known broken.
967
892
        self.control_files.lock_write()
974
899
 
975
900
    def unlock(self):
976
901
        # TODO: test for failed two phase locks. This is known broken.
977
 
        try:
978
 
            self.repository.unlock()
979
 
        finally:
980
 
            self.control_files.unlock()
981
 
        
 
902
        self.repository.unlock()
 
903
        self.control_files.unlock()
 
904
 
982
905
    def peek_lock_mode(self):
983
906
        if self.control_files._lock_count == 0:
984
907
            return None
985
908
        else:
986
909
            return self.control_files._lock_mode
987
910
 
988
 
    def get_physical_lock_status(self):
989
 
        return self.control_files.get_physical_lock_status()
990
 
 
991
911
    @needs_read_lock
992
912
    def print_file(self, file, revision_id):
993
913
        """See Branch.print_file."""
1007
927
        """See Branch.set_revision_history."""
1008
928
        self.control_files.put_utf8(
1009
929
            'revision-history', '\n'.join(rev_history))
1010
 
        transaction = self.get_transaction()
1011
 
        history = transaction.map.find_revision_history()
1012
 
        if history is not None:
1013
 
            # update the revision history in the identity map.
1014
 
            history[:] = list(rev_history)
1015
 
            # this call is disabled because revision_history is 
1016
 
            # not really an object yet, and the transaction is for objects.
1017
 
            # transaction.register_dirty(history)
1018
 
        else:
1019
 
            transaction.map.add_revision_history(rev_history)
1020
 
            # this call is disabled because revision_history is 
1021
 
            # not really an object yet, and the transaction is for objects.
1022
 
            # transaction.register_clean(history)
1023
930
 
1024
931
    def get_revision_delta(self, revno):
1025
932
        """Return the delta for one revision.
1044
951
    @needs_read_lock
1045
952
    def revision_history(self):
1046
953
        """See Branch.revision_history."""
 
954
        # FIXME are transactions bound to control files ? RBC 20051121
1047
955
        transaction = self.get_transaction()
1048
956
        history = transaction.map.find_revision_history()
1049
957
        if history is not None:
1057
965
        # transaction.register_clean(history, precious=True)
1058
966
        return list(history)
1059
967
 
1060
 
    @needs_write_lock
1061
968
    def update_revisions(self, other, stop_revision=None):
1062
969
        """See Branch.update_revisions."""
1063
 
        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)
1064
984
        try:
1065
 
            if stop_revision is None:
1066
 
                stop_revision = other.last_revision()
1067
 
                if stop_revision is None:
1068
 
                    # if there are no commits, we're done.
1069
 
                    return
1070
 
            # whats the current last revision, before we fetch [and change it
1071
 
            # possibly]
1072
 
            last_rev = self.last_revision()
1073
 
            # we fetch here regardless of whether we need to so that we pickup
1074
 
            # filled in ghosts.
1075
 
            self.fetch(other, stop_revision)
1076
 
            my_ancestry = self.repository.get_ancestry(last_rev)
1077
 
            if stop_revision in my_ancestry:
1078
 
                # last_revision is a descendant of stop_revision
1079
 
                return
1080
 
            # stop_revision must be a descendant of last_revision
1081
 
            stop_graph = self.repository.get_revision_graph(stop_revision)
1082
 
            if last_rev is not None and last_rev not in stop_graph:
1083
 
                # our previous tip is not merged into stop_revision
1084
 
                raise errors.DivergedBranches(self, other)
1085
 
            # make a new revision history from the graph
1086
 
            current_rev_id = stop_revision
1087
 
            new_history = []
1088
 
            while current_rev_id not in (None, NULL_REVISION):
1089
 
                new_history.append(current_rev_id)
1090
 
                current_rev_id_parents = stop_graph[current_rev_id]
1091
 
                try:
1092
 
                    current_rev_id = current_rev_id_parents[0]
1093
 
                except IndexError:
1094
 
                    current_rev_id = None
1095
 
            new_history.reverse()
1096
 
            self.set_revision_history(new_history)
1097
 
        finally:
1098
 
            other.unlock()
1099
 
 
 
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
        
1100
999
    def basis_tree(self):
1101
1000
        """See Branch.basis_tree."""
1102
1001
        return self.repository.revision_tree(self.last_revision())
1159
1058
        # FIXUP this and get_parent in a future branch format bump:
1160
1059
        # read and rewrite the file, and have the new format code read
1161
1060
        # using .get not .get_utf8. RBC 20060125
1162
 
        if url is None:
1163
 
            self.control_files._transport.delete('parent')
1164
 
        else:
1165
 
            self.control_files.put_utf8('parent', url + '\n')
 
1061
        self.control_files.put_utf8('parent', url + '\n')
1166
1062
 
1167
1063
    def tree_config(self):
1168
1064
        return TreeConfig(self)
1169
1065
 
1170
 
 
1171
 
class BzrBranch5(BzrBranch):
1172
 
    """A format 5 branch. This supports new features over plan branches.
1173
 
 
1174
 
    It has support for a master_branch which is the data for bound branches.
1175
 
    """
1176
 
 
1177
 
    def __init__(self,
1178
 
                 _format,
1179
 
                 _control_files,
1180
 
                 a_bzrdir,
1181
 
                 _repository):
1182
 
        super(BzrBranch5, self).__init__(_format=_format,
1183
 
                                         _control_files=_control_files,
1184
 
                                         a_bzrdir=a_bzrdir,
1185
 
                                         _repository=_repository)
1186
 
        
1187
 
    @needs_write_lock
1188
 
    def pull(self, source, overwrite=False, stop_revision=None):
1189
 
        """Updates branch.pull to be bound branch aware."""
1190
 
        bound_location = self.get_bound_location()
1191
 
        if source.base != bound_location:
1192
 
            # not pulling from master, so we need to update master.
1193
 
            master_branch = self.get_master_branch()
1194
 
            if master_branch:
1195
 
                master_branch.pull(source)
1196
 
                source = master_branch
1197
 
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1198
 
 
1199
 
    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
1200
1070
        try:
1201
 
            return self.control_files.get_utf8('bound').read()[:-1]
1202
 
        except errors.NoSuchFile:
1203
 
            return None
 
1071
            idx = history.index(revision_id)
 
1072
        except ValueError:
 
1073
            raise InvalidRevisionId(revision_id=revision, branch=self)
 
1074
        return history[:idx+1]
1204
1075
 
1205
1076
    @needs_read_lock
1206
 
    def get_master_branch(self):
1207
 
        """Return the branch we are bound to.
1208
 
        
1209
 
        :return: Either a Branch, or None
1210
 
 
1211
 
        This could memoise the branch, but if thats done
1212
 
        it must be revalidated on each new lock.
1213
 
        So for now we just dont memoise it.
1214
 
        # RBC 20060304 review this decision.
1215
 
        """
1216
 
        bound_loc = self.get_bound_location()
1217
 
        if not bound_loc:
1218
 
            return None
1219
 
        try:
1220
 
            return Branch.open(bound_loc)
1221
 
        except (errors.NotBranchError, errors.ConnectionError), e:
1222
 
            raise errors.BoundBranchConnectionFailure(
1223
 
                    self, bound_loc, e)
1224
 
 
1225
 
    @needs_write_lock
1226
 
    def set_bound_location(self, location):
1227
 
        """Set the target where this branch is bound to.
1228
 
 
1229
 
        :param location: URL to the target branch
1230
 
        """
1231
 
        if location:
1232
 
            self.control_files.put_utf8('bound', location+'\n')
1233
 
        else:
1234
 
            try:
1235
 
                self.control_files._transport.delete('bound')
1236
 
            except NoSuchFile:
1237
 
                return False
1238
 
            return True
1239
 
 
1240
 
    @needs_write_lock
1241
 
    def bind(self, other):
1242
 
        """Bind the local branch the other branch.
1243
 
 
1244
 
        :param other: The branch to bind to
1245
 
        :type other: Branch
1246
 
        """
1247
 
        # TODO: jam 20051230 Consider checking if the target is bound
1248
 
        #       It is debatable whether you should be able to bind to
1249
 
        #       a branch which is itself bound.
1250
 
        #       Committing is obviously forbidden,
1251
 
        #       but binding itself may not be.
1252
 
        #       Since we *have* to check at commit time, we don't
1253
 
        #       *need* to check here
1254
 
        self.pull(other)
1255
 
 
1256
 
        # we are now equal to or a suffix of other.
1257
 
 
1258
 
        # Since we have 'pulled' from the remote location,
1259
 
        # now we should try to pull in the opposite direction
1260
 
        # in case the local tree has more revisions than the
1261
 
        # remote one.
1262
 
        # There may be a different check you could do here
1263
 
        # rather than actually trying to install revisions remotely.
1264
 
        # TODO: capture an exception which indicates the remote branch
1265
 
        #       is not writeable. 
1266
 
        #       If it is up-to-date, this probably should not be a failure
1267
 
        
1268
 
        # lock other for write so the revision-history syncing cannot race
1269
 
        other.lock_write()
1270
 
        try:
1271
 
            other.pull(self)
1272
 
            # if this does not error, other now has the same last rev we do
1273
 
            # it can only error if the pull from other was concurrent with
1274
 
            # a commit to other from someone else.
1275
 
 
1276
 
            # until we ditch revision-history, we need to sync them up:
1277
 
            self.set_revision_history(other.revision_history())
1278
 
            # now other and self are up to date with each other and have the
1279
 
            # same revision-history.
1280
 
        finally:
1281
 
            other.unlock()
1282
 
 
1283
 
        self.set_bound_location(other.base)
1284
 
 
1285
 
    @needs_write_lock
1286
 
    def unbind(self):
1287
 
        """If bound, unbind"""
1288
 
        return self.set_bound_location(None)
1289
 
 
1290
 
    @needs_write_lock
1291
 
    def update(self):
1292
 
        """Synchronise this branch with the master branch if any. 
1293
 
 
1294
 
        :return: None or the last_revision that was pivoted out during the
1295
 
                 update.
1296
 
        """
1297
 
        master = self.get_master_branch()
1298
 
        if master is not None:
1299
 
            old_tip = self.last_revision()
1300
 
            self.pull(master, overwrite=True)
1301
 
            if old_tip in self.repository.get_ancestry(self.last_revision()):
1302
 
                return None
1303
 
            return old_tip
1304
 
        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
1305
1099
 
1306
1100
 
1307
1101
class BranchTestProviderAdapter(object):