~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 00:23:23 UTC
  • mfrom: (2070 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2071.
  • Revision ID: john@arbash-meinel.com-20061011002323-82ba88c293d7caff
[merge] bzr.dev 2070

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
23
26
import bzrlib
24
 
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
25
 
        tree, \
26
 
        ui, \
27
 
        urlutils
28
 
from bzrlib.config import TreeConfig
 
27
from bzrlib import (
 
28
        bzrdir,
 
29
        cache_utf8,
 
30
        errors,
 
31
        lockdir,
 
32
        lockable_files,
 
33
        osutils,
 
34
        revision as _mod_revision,
 
35
        transport,
 
36
        tree,
 
37
        ui,
 
38
        urlutils,
 
39
        )
 
40
from bzrlib.config import BranchConfig, TreeConfig
 
41
from bzrlib.lockable_files import LockableFiles, TransportLock
 
42
""")
 
43
 
29
44
from bzrlib.decorators import needs_read_lock, needs_write_lock
30
 
import bzrlib.errors as errors
31
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
32
 
                           HistoryMissing, InvalidRevisionId, 
33
 
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
45
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
 
46
                           HistoryMissing, InvalidRevisionId,
 
47
                           InvalidRevisionNumber, LockError, NoSuchFile,
34
48
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
35
 
                           NotBranchError, UninitializableFormat, 
36
 
                           UnlistableStore, UnlistableBranch, 
 
49
                           NotBranchError, UninitializableFormat,
 
50
                           UnlistableStore, UnlistableBranch,
37
51
                           )
38
 
from bzrlib.lockable_files import LockableFiles, TransportLock
39
52
from bzrlib.symbol_versioning import (deprecated_function,
40
53
                                      deprecated_method,
41
54
                                      DEPRECATED_PARAMETER,
130
143
        """
131
144
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
132
145
 
 
146
    @deprecated_function(zero_eight)
133
147
    def setup_caching(self, cache_root):
134
148
        """Subclasses that care about caching should override this, and set
135
149
        up cached stores located under cache_root.
 
150
        
 
151
        NOTE: This is unused.
136
152
        """
137
 
        # seems to be unused, 2006-01-13 mbp
138
 
        warn('%s is deprecated' % self.setup_caching)
139
 
        self.cache_root = cache_root
 
153
        pass
140
154
 
141
155
    def get_config(self):
142
 
        return bzrlib.config.BranchConfig(self)
 
156
        return BranchConfig(self)
143
157
 
144
158
    def _get_nick(self):
145
159
        return self.get_config().get_nickname()
150
164
    nick = property(_get_nick, _set_nick)
151
165
 
152
166
    def is_locked(self):
153
 
        raise NotImplementedError('is_locked is abstract')
 
167
        raise NotImplementedError(self.is_locked)
154
168
 
155
169
    def lock_write(self):
156
 
        raise NotImplementedError('lock_write is abstract')
 
170
        raise NotImplementedError(self.lock_write)
157
171
 
158
172
    def lock_read(self):
159
 
        raise NotImplementedError('lock_read is abstract')
 
173
        raise NotImplementedError(self.lock_read)
160
174
 
161
175
    def unlock(self):
162
 
        raise NotImplementedError('unlock is abstract')
 
176
        raise NotImplementedError(self.unlock)
163
177
 
164
178
    def peek_lock_mode(self):
165
179
        """Return lock mode for the Branch: 'r', 'w' or None"""
166
180
        raise NotImplementedError(self.peek_lock_mode)
167
181
 
168
182
    def get_physical_lock_status(self):
169
 
        raise NotImplementedError('get_physical_lock_status is abstract')
 
183
        raise NotImplementedError(self.get_physical_lock_status)
170
184
 
171
185
    def abspath(self, name):
172
186
        """Return absolute filename for something in the branch
174
188
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
175
189
        method and not a tree method.
176
190
        """
177
 
        raise NotImplementedError('abspath is abstract')
 
191
        raise NotImplementedError(self.abspath)
178
192
 
179
193
    def bind(self, other):
180
194
        """Bind the local branch the other branch.
213
227
                    last_revision = from_history[-1]
214
228
                else:
215
229
                    # no history in the source branch
216
 
                    last_revision = revision.NULL_REVISION
 
230
                    last_revision = _mod_revision.NULL_REVISION
217
231
            return self.repository.fetch(from_branch.repository,
218
232
                                         revision_id=last_revision,
219
233
                                         pb=nested_pb)
271
285
 
272
286
    def get_root_id(self):
273
287
        """Return the id of this branches root"""
274
 
        raise NotImplementedError('get_root_id is abstract')
 
288
        raise NotImplementedError(self.get_root_id)
275
289
 
276
290
    def print_file(self, file, revision_id):
277
291
        """Print `file` to stdout."""
278
 
        raise NotImplementedError('print_file is abstract')
 
292
        raise NotImplementedError(self.print_file)
279
293
 
280
294
    def append_revision(self, *revision_ids):
281
 
        raise NotImplementedError('append_revision is abstract')
 
295
        raise NotImplementedError(self.append_revision)
282
296
 
283
297
    def set_revision_history(self, rev_history):
284
 
        raise NotImplementedError('set_revision_history is abstract')
 
298
        raise NotImplementedError(self.set_revision_history)
285
299
 
286
300
    def revision_history(self):
287
301
        """Return sequence of revision hashes on to this branch."""
288
 
        raise NotImplementedError('revision_history is abstract')
 
302
        raise NotImplementedError(self.revision_history)
289
303
 
290
304
    def revno(self):
291
305
        """Return current revision number for this branch.
300
314
        raise errors.UpgradeRequired(self.base)
301
315
 
302
316
    def last_revision(self):
303
 
        """Return last patch hash, or None if no history."""
 
317
        """Return last revision id, or None"""
304
318
        ph = self.revision_history()
305
319
        if ph:
306
320
            return ph[-1]
337
351
        :param stop_revision: Updated until the given revision
338
352
        :return: None
339
353
        """
340
 
        raise NotImplementedError('update_revisions is abstract')
 
354
        raise NotImplementedError(self.update_revisions)
341
355
 
342
356
    def revision_id_to_revno(self, revision_id):
343
357
        """Given a revision id, return its revno"""
355
369
            return None
356
370
        if history is None:
357
371
            history = self.revision_history()
358
 
        elif revno <= 0 or revno > len(history):
 
372
        if revno <= 0 or revno > len(history):
359
373
            raise bzrlib.errors.NoSuchRevision(self, revno)
360
374
        return history[revno - 1]
361
375
 
362
376
    def pull(self, source, overwrite=False, stop_revision=None):
363
 
        raise NotImplementedError('pull is abstract')
 
377
        raise NotImplementedError(self.pull)
364
378
 
365
379
    def basis_tree(self):
366
 
        """Return `Tree` object for last revision.
367
 
 
368
 
        If there are no revisions yet, return an `EmptyTree`.
369
 
        """
 
380
        """Return `Tree` object for last revision."""
370
381
        return self.repository.revision_tree(self.last_revision())
371
382
 
372
383
    def rename_one(self, from_rel, to_rel):
374
385
 
375
386
        This can change the directory or the filename or both.
376
387
        """
377
 
        raise NotImplementedError('rename_one is abstract')
 
388
        raise NotImplementedError(self.rename_one)
378
389
 
379
390
    def move(self, from_paths, to_name):
380
391
        """Rename files.
390
401
        This returns a list of (from_path, to_path) pairs for each
391
402
        entry that is moved.
392
403
        """
393
 
        raise NotImplementedError('move is abstract')
 
404
        raise NotImplementedError(self.move)
394
405
 
395
406
    def get_parent(self):
396
407
        """Return the parent location of the branch.
399
410
        pattern is that the user can override it by specifying a
400
411
        location.
401
412
        """
402
 
        raise NotImplementedError('get_parent is abstract')
 
413
        raise NotImplementedError(self.get_parent)
403
414
 
404
415
    def get_submit_branch(self):
405
416
        """Return the submit location of the branch.
421
432
 
422
433
    def get_push_location(self):
423
434
        """Return the None or the location to push this branch to."""
424
 
        raise NotImplementedError('get_push_location is abstract')
 
435
        raise NotImplementedError(self.get_push_location)
425
436
 
426
437
    def set_push_location(self, location):
427
438
        """Set a new push location for this branch."""
428
 
        raise NotImplementedError('set_push_location is abstract')
 
439
        raise NotImplementedError(self.set_push_location)
429
440
 
430
441
    def set_parent(self, url):
431
 
        raise NotImplementedError('set_parent is abstract')
 
442
        raise NotImplementedError(self.set_parent)
432
443
 
433
444
    @needs_write_lock
434
445
    def update(self):
537
548
                rev = self.repository.get_revision(revision_id)
538
549
                new_history = rev.get_history(self.repository)[1:]
539
550
        destination.set_revision_history(new_history)
540
 
        parent = self.get_parent()
541
 
        if parent:
542
 
            destination.set_parent(parent)
 
551
        try:
 
552
            parent = self.get_parent()
 
553
        except errors.InaccessibleParent, e:
 
554
            mutter('parent was not accessible to copy: %s', e)
 
555
        else:
 
556
            if parent:
 
557
                destination.set_parent(parent)
543
558
 
544
559
    @needs_read_lock
545
560
    def check(self):
571
586
            mainline_parent_id = revision_id
572
587
        return BranchCheckResult(self)
573
588
 
 
589
    def _get_checkout_format(self):
 
590
        """Return the most suitable metadir for a checkout of this branch.
 
591
        Weaves are used if this branch's repostory uses weaves.
 
592
        """
 
593
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
 
594
            from bzrlib import repository
 
595
            format = bzrdir.BzrDirMetaFormat1()
 
596
            format.repository_format = repository.RepositoryFormat7()
 
597
        else:
 
598
            format = self.repository.bzrdir.cloning_metadir()
 
599
        return format
 
600
 
 
601
    def create_checkout(self, to_location, revision_id=None, 
 
602
                        lightweight=False):
 
603
        """Create a checkout of a branch.
 
604
        
 
605
        :param to_location: The url to produce the checkout at
 
606
        :param revision_id: The revision to check out
 
607
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
608
        produce a bound branch (heavyweight checkout)
 
609
        :return: The tree of the created checkout
 
610
        """
 
611
        t = transport.get_transport(to_location)
 
612
        try:
 
613
            t.mkdir('.')
 
614
        except errors.FileExists:
 
615
            pass
 
616
        if lightweight:
 
617
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
618
            BranchReferenceFormat().initialize(checkout, self)
 
619
        else:
 
620
            format = self._get_checkout_format()
 
621
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
622
                to_location, force_new_tree=False, format=format)
 
623
            checkout = checkout_branch.bzrdir
 
624
            checkout_branch.bind(self)
 
625
            # pull up to the specified revision_id to set the initial 
 
626
            # branch tip correctly, and seed it with history.
 
627
            checkout_branch.pull(self, stop_revision=revision_id)
 
628
        return checkout.create_workingtree(revision_id)
 
629
 
574
630
 
575
631
class BranchFormat(object):
576
632
    """An encapsulation of the initialization and open routines for a format.
678
734
        utf8_files = [('revision-history', ''),
679
735
                      ('branch-name', ''),
680
736
                      ]
681
 
        control_files = LockableFiles(branch_transport, 'branch-lock',
682
 
                                      TransportLock)
 
737
        control_files = lockable_files.LockableFiles(branch_transport,
 
738
                             'branch-lock', lockable_files.TransportLock)
683
739
        control_files.create_lock()
684
740
        control_files.lock_write()
685
741
        try:
739
795
        utf8_files = [('revision-history', ''),
740
796
                      ('branch-name', ''),
741
797
                      ]
742
 
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
 
798
        control_files = lockable_files.LockableFiles(branch_transport, 'lock',
 
799
                                                     lockdir.LockDir)
743
800
        control_files.create_lock()
744
801
        control_files.lock_write()
745
802
        control_files.put_utf8('format', self.get_format_string())
764
821
            format = BranchFormat.find_format(a_bzrdir)
765
822
            assert format.__class__ == self.__class__
766
823
        transport = a_bzrdir.get_branch_transport(None)
767
 
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
 
824
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
825
                                                     lockdir.LockDir)
768
826
        return BzrBranch5(_format=self,
769
827
                          _control_files=control_files,
770
828
                          a_bzrdir=a_bzrdir,
801
859
            raise errors.UninitializableFormat(self)
802
860
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
803
861
        branch_transport = a_bzrdir.get_branch_transport(self)
804
 
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
805
 
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
806
 
        branch_transport.put('format', StringIO(self.get_format_string()))
 
862
        branch_transport.put_bytes('location',
 
863
            target_branch.bzrdir.root_transport.base)
 
864
        branch_transport.put_bytes('format', self.get_format_string())
807
865
        return self.open(a_bzrdir, _found=True)
808
866
 
809
867
    def __init__(self):
920
978
 
921
979
    __repr__ = __str__
922
980
 
923
 
    def __del__(self):
924
 
        # TODO: It might be best to do this somewhere else,
925
 
        # but it is nice for a Branch object to automatically
926
 
        # cache it's information.
927
 
        # Alternatively, we could have the Transport objects cache requests
928
 
        # See the earlier discussion about how major objects (like Branch)
929
 
        # should never expect their __del__ function to run.
930
 
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
931
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
932
 
            try:
933
 
                osutils.rmtree(self.cache_root)
934
 
            except:
935
 
                pass
936
 
            self.cache_root = None
937
 
 
938
981
    def _get_base(self):
939
982
        return self._base
940
983
 
1063
1106
        transaction = self.get_transaction()
1064
1107
        history = transaction.map.find_revision_history()
1065
1108
        if history is not None:
1066
 
            mutter("cache hit for revision-history in %s", self)
 
1109
            # mutter("cache hit for revision-history in %s", self)
1067
1110
            return list(history)
1068
 
        history = [l.rstrip('\r\n') for l in
1069
 
                self.control_files.get_utf8('revision-history').readlines()]
 
1111
        decode_utf8 = cache_utf8.decode
 
1112
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1113
                self.control_files.get('revision-history').readlines()]
1070
1114
        transaction.map.add_revision_history(history)
1071
1115
        # this call is disabled because revision_history is 
1072
1116
        # not really an object yet, and the transaction is for objects.
1092
1136
        # make a new revision history from the graph
1093
1137
        current_rev_id = revision_id
1094
1138
        new_history = []
1095
 
        while current_rev_id not in (None, revision.NULL_REVISION):
 
1139
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1096
1140
            new_history.append(current_rev_id)
1097
1141
            current_rev_id_parents = stop_graph[current_rev_id]
1098
1142
            try:
1148
1192
        try:
1149
1193
            old_count = len(self.revision_history())
1150
1194
            try:
1151
 
                self.update_revisions(source,stop_revision)
 
1195
                self.update_revisions(source, stop_revision)
1152
1196
            except DivergedBranches:
1153
1197
                if not overwrite:
1154
1198
                    raise
1173
1217
            # turn it into a url
1174
1218
            if parent.startswith('/'):
1175
1219
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
1176
 
            return urlutils.join(self.base[:-1], parent)
 
1220
            try:
 
1221
                return urlutils.join(self.base[:-1], parent)
 
1222
            except errors.InvalidURLJoin, e:
 
1223
                raise errors.InaccessibleParent(parent, self.base)
1177
1224
        return None
1178
1225
 
1179
1226
    def get_push_location(self):
1206
1253
                        "use bzrlib.urlutils.escape")
1207
1254
                    
1208
1255
            url = urlutils.relative_url(self.base, url)
1209
 
            self.control_files.put('parent', url + '\n')
 
1256
            self.control_files.put('parent', StringIO(url + '\n'))
1210
1257
 
1211
1258
    @deprecated_function(zero_nine)
1212
1259
    def tree_config(self):
1286
1333
 
1287
1334
    @needs_write_lock
1288
1335
    def bind(self, other):
1289
 
        """Bind the local branch the other branch.
 
1336
        """Bind this branch to the branch other.
1290
1337
 
 
1338
        This does not push or pull data between the branches, though it does
 
1339
        check for divergence to raise an error when the branches are not
 
1340
        either the same, or one a prefix of the other. That behaviour may not
 
1341
        be useful, so that check may be removed in future.
 
1342
        
1291
1343
        :param other: The branch to bind to
1292
1344
        :type other: Branch
1293
1345
        """
1298
1350
        #       but binding itself may not be.
1299
1351
        #       Since we *have* to check at commit time, we don't
1300
1352
        #       *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
 
 
 
1353
 
 
1354
        # we want to raise diverged if:
 
1355
        # last_rev is not in the other_last_rev history, AND
 
1356
        # other_last_rev is not in our history, and do it without pulling
 
1357
        # history around
 
1358
        last_rev = self.last_revision()
 
1359
        if last_rev is not None:
 
1360
            other.lock_read()
 
1361
            try:
 
1362
                other_last_rev = other.last_revision()
 
1363
                if other_last_rev is not None:
 
1364
                    # neither branch is new, we have to do some work to
 
1365
                    # ascertain diversion.
 
1366
                    remote_graph = other.repository.get_revision_graph(
 
1367
                        other_last_rev)
 
1368
                    local_graph = self.repository.get_revision_graph(last_rev)
 
1369
                    if (last_rev not in remote_graph and
 
1370
                        other_last_rev not in local_graph):
 
1371
                        raise errors.DivergedBranches(self, other)
 
1372
            finally:
 
1373
                other.unlock()
1330
1374
        self.set_bound_location(other.base)
1331
1375
 
1332
1376
    @needs_write_lock