~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Aaron Bentley
  • Date: 2006-09-26 13:28:39 UTC
  • mto: This revision was merged to the branch mainline in revision 2048.
  • Revision ID: abentley@panoramicfeedback.com-20060926132839-cee32159ab268eeb
Changes from review

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
from warnings import warn
22
22
 
23
23
import bzrlib
24
 
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
25
 
        tree, \
26
 
        ui, \
27
 
        urlutils
 
24
from bzrlib import (
 
25
        bzrdir,
 
26
        cache_utf8,
 
27
        errors,
 
28
        lockdir,
 
29
        osutils,
 
30
        revision,
 
31
        transport,
 
32
        tree,
 
33
        ui,
 
34
        urlutils,
 
35
        )
28
36
from bzrlib.config import TreeConfig
29
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
30
38
import bzrlib.errors as errors
130
138
        """
131
139
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
132
140
 
 
141
    @deprecated_function(zero_eight)
133
142
    def setup_caching(self, cache_root):
134
143
        """Subclasses that care about caching should override this, and set
135
144
        up cached stores located under cache_root.
 
145
        
 
146
        NOTE: This is unused.
136
147
        """
137
 
        # seems to be unused, 2006-01-13 mbp
138
 
        warn('%s is deprecated' % self.setup_caching)
139
 
        self.cache_root = cache_root
 
148
        pass
140
149
 
141
150
    def get_config(self):
142
151
        return bzrlib.config.BranchConfig(self)
150
159
    nick = property(_get_nick, _set_nick)
151
160
 
152
161
    def is_locked(self):
153
 
        raise NotImplementedError('is_locked is abstract')
 
162
        raise NotImplementedError(self.is_locked)
154
163
 
155
164
    def lock_write(self):
156
 
        raise NotImplementedError('lock_write is abstract')
 
165
        raise NotImplementedError(self.lock_write)
157
166
 
158
167
    def lock_read(self):
159
 
        raise NotImplementedError('lock_read is abstract')
 
168
        raise NotImplementedError(self.lock_read)
160
169
 
161
170
    def unlock(self):
162
 
        raise NotImplementedError('unlock is abstract')
 
171
        raise NotImplementedError(self.unlock)
163
172
 
164
173
    def peek_lock_mode(self):
165
174
        """Return lock mode for the Branch: 'r', 'w' or None"""
166
175
        raise NotImplementedError(self.peek_lock_mode)
167
176
 
168
177
    def get_physical_lock_status(self):
169
 
        raise NotImplementedError('get_physical_lock_status is abstract')
 
178
        raise NotImplementedError(self.get_physical_lock_status)
170
179
 
171
180
    def abspath(self, name):
172
181
        """Return absolute filename for something in the branch
174
183
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
175
184
        method and not a tree method.
176
185
        """
177
 
        raise NotImplementedError('abspath is abstract')
 
186
        raise NotImplementedError(self.abspath)
178
187
 
179
188
    def bind(self, other):
180
189
        """Bind the local branch the other branch.
271
280
 
272
281
    def get_root_id(self):
273
282
        """Return the id of this branches root"""
274
 
        raise NotImplementedError('get_root_id is abstract')
 
283
        raise NotImplementedError(self.get_root_id)
275
284
 
276
285
    def print_file(self, file, revision_id):
277
286
        """Print `file` to stdout."""
278
 
        raise NotImplementedError('print_file is abstract')
 
287
        raise NotImplementedError(self.print_file)
279
288
 
280
289
    def append_revision(self, *revision_ids):
281
 
        raise NotImplementedError('append_revision is abstract')
 
290
        raise NotImplementedError(self.append_revision)
282
291
 
283
292
    def set_revision_history(self, rev_history):
284
 
        raise NotImplementedError('set_revision_history is abstract')
 
293
        raise NotImplementedError(self.set_revision_history)
285
294
 
286
295
    def revision_history(self):
287
296
        """Return sequence of revision hashes on to this branch."""
288
 
        raise NotImplementedError('revision_history is abstract')
 
297
        raise NotImplementedError(self.revision_history)
289
298
 
290
299
    def revno(self):
291
300
        """Return current revision number for this branch.
300
309
        raise errors.UpgradeRequired(self.base)
301
310
 
302
311
    def last_revision(self):
303
 
        """Return last patch hash, or None if no history."""
 
312
        """Return last revision id, or None"""
304
313
        ph = self.revision_history()
305
314
        if ph:
306
315
            return ph[-1]
337
346
        :param stop_revision: Updated until the given revision
338
347
        :return: None
339
348
        """
340
 
        raise NotImplementedError('update_revisions is abstract')
 
349
        raise NotImplementedError(self.update_revisions)
341
350
 
342
351
    def revision_id_to_revno(self, revision_id):
343
352
        """Given a revision id, return its revno"""
355
364
            return None
356
365
        if history is None:
357
366
            history = self.revision_history()
358
 
        elif revno <= 0 or revno > len(history):
 
367
        if revno <= 0 or revno > len(history):
359
368
            raise bzrlib.errors.NoSuchRevision(self, revno)
360
369
        return history[revno - 1]
361
370
 
362
371
    def pull(self, source, overwrite=False, stop_revision=None):
363
 
        raise NotImplementedError('pull is abstract')
 
372
        raise NotImplementedError(self.pull)
364
373
 
365
374
    def basis_tree(self):
366
 
        """Return `Tree` object for last revision.
367
 
 
368
 
        If there are no revisions yet, return an `EmptyTree`.
369
 
        """
 
375
        """Return `Tree` object for last revision."""
370
376
        return self.repository.revision_tree(self.last_revision())
371
377
 
372
378
    def rename_one(self, from_rel, to_rel):
374
380
 
375
381
        This can change the directory or the filename or both.
376
382
        """
377
 
        raise NotImplementedError('rename_one is abstract')
 
383
        raise NotImplementedError(self.rename_one)
378
384
 
379
385
    def move(self, from_paths, to_name):
380
386
        """Rename files.
390
396
        This returns a list of (from_path, to_path) pairs for each
391
397
        entry that is moved.
392
398
        """
393
 
        raise NotImplementedError('move is abstract')
 
399
        raise NotImplementedError(self.move)
394
400
 
395
401
    def get_parent(self):
396
402
        """Return the parent location of the branch.
399
405
        pattern is that the user can override it by specifying a
400
406
        location.
401
407
        """
402
 
        raise NotImplementedError('get_parent is abstract')
 
408
        raise NotImplementedError(self.get_parent)
403
409
 
404
410
    def get_submit_branch(self):
405
411
        """Return the submit location of the branch.
421
427
 
422
428
    def get_push_location(self):
423
429
        """Return the None or the location to push this branch to."""
424
 
        raise NotImplementedError('get_push_location is abstract')
 
430
        raise NotImplementedError(self.get_push_location)
425
431
 
426
432
    def set_push_location(self, location):
427
433
        """Set a new push location for this branch."""
428
 
        raise NotImplementedError('set_push_location is abstract')
 
434
        raise NotImplementedError(self.set_push_location)
429
435
 
430
436
    def set_parent(self, url):
431
 
        raise NotImplementedError('set_parent is abstract')
 
437
        raise NotImplementedError(self.set_parent)
432
438
 
433
439
    @needs_write_lock
434
440
    def update(self):
537
543
                rev = self.repository.get_revision(revision_id)
538
544
                new_history = rev.get_history(self.repository)[1:]
539
545
        destination.set_revision_history(new_history)
540
 
        parent = self.get_parent()
541
 
        if parent:
542
 
            destination.set_parent(parent)
 
546
        try:
 
547
            parent = self.get_parent()
 
548
        except errors.InaccessibleParent, e:
 
549
            mutter('parent was not accessible to copy: %s', e)
 
550
        else:
 
551
            if parent:
 
552
                destination.set_parent(parent)
543
553
 
544
554
    @needs_read_lock
545
555
    def check(self):
571
581
            mainline_parent_id = revision_id
572
582
        return BranchCheckResult(self)
573
583
 
 
584
    def _get_checkout_format(self):
 
585
        """Return the most suitable metadir for a checkout of this branch.
 
586
        Weaves are used if this branch's repostory uses weaves.
 
587
        """
 
588
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
 
589
            from bzrlib import repository
 
590
            format = bzrdir.BzrDirMetaFormat1()
 
591
            format.repository_format = repository.RepositoryFormat7()
 
592
        else:
 
593
            format = self.repository.bzrdir.cloning_metadir()
 
594
        return format
 
595
 
 
596
    def create_checkout(self, to_location, revision_id=None, 
 
597
                        lightweight=False):
 
598
        """Create a checkout of a branch.
 
599
        
 
600
        :param to_location: The url to produce the checkout at
 
601
        :param revision_id: The revision to check out
 
602
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
603
        produce a bound branch (heavyweight checkout)
 
604
        :return: The tree of the created checkout
 
605
        """
 
606
        t = transport.get_transport(to_location)
 
607
        try:
 
608
            t.mkdir('.')
 
609
        except errors.FileExists:
 
610
            pass
 
611
        if lightweight:
 
612
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
613
            BranchReferenceFormat().initialize(checkout, self)
 
614
        else:
 
615
            format = self._get_checkout_format()
 
616
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
617
                to_location, force_new_tree=False, format=format)
 
618
            checkout = checkout_branch.bzrdir
 
619
            checkout_branch.bind(self)
 
620
            # pull up to the specified revision_id to set the initial 
 
621
            # branch tip correctly, and seed it with history.
 
622
            checkout_branch.pull(self, stop_revision=revision_id)
 
623
        return checkout.create_workingtree(revision_id)
 
624
 
574
625
 
575
626
class BranchFormat(object):
576
627
    """An encapsulation of the initialization and open routines for a format.
801
852
            raise errors.UninitializableFormat(self)
802
853
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
803
854
        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()))
 
855
        branch_transport.put_bytes('location',
 
856
            target_branch.bzrdir.root_transport.base)
 
857
        branch_transport.put_bytes('format', self.get_format_string())
807
858
        return self.open(a_bzrdir, _found=True)
808
859
 
809
860
    def __init__(self):
920
971
 
921
972
    __repr__ = __str__
922
973
 
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
974
    def _get_base(self):
939
975
        return self._base
940
976
 
1063
1099
        transaction = self.get_transaction()
1064
1100
        history = transaction.map.find_revision_history()
1065
1101
        if history is not None:
1066
 
            mutter("cache hit for revision-history in %s", self)
 
1102
            # mutter("cache hit for revision-history in %s", self)
1067
1103
            return list(history)
1068
 
        history = [l.rstrip('\r\n') for l in
1069
 
                self.control_files.get_utf8('revision-history').readlines()]
 
1104
        decode_utf8 = cache_utf8.decode
 
1105
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1106
                self.control_files.get('revision-history').readlines()]
1070
1107
        transaction.map.add_revision_history(history)
1071
1108
        # this call is disabled because revision_history is 
1072
1109
        # not really an object yet, and the transaction is for objects.
1148
1185
        try:
1149
1186
            old_count = len(self.revision_history())
1150
1187
            try:
1151
 
                self.update_revisions(source,stop_revision)
 
1188
                self.update_revisions(source, stop_revision)
1152
1189
            except DivergedBranches:
1153
1190
                if not overwrite:
1154
1191
                    raise
1173
1210
            # turn it into a url
1174
1211
            if parent.startswith('/'):
1175
1212
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
1176
 
            return urlutils.join(self.base[:-1], parent)
 
1213
            try:
 
1214
                return urlutils.join(self.base[:-1], parent)
 
1215
            except errors.InvalidURLJoin, e:
 
1216
                raise errors.InaccessibleParent(parent, self.base)
1177
1217
        return None
1178
1218
 
1179
1219
    def get_push_location(self):
1206
1246
                        "use bzrlib.urlutils.escape")
1207
1247
                    
1208
1248
            url = urlutils.relative_url(self.base, url)
1209
 
            self.control_files.put('parent', url + '\n')
 
1249
            self.control_files.put('parent', StringIO(url + '\n'))
1210
1250
 
1211
1251
    @deprecated_function(zero_nine)
1212
1252
    def tree_config(self):
1286
1326
 
1287
1327
    @needs_write_lock
1288
1328
    def bind(self, other):
1289
 
        """Bind the local branch the other branch.
 
1329
        """Bind this branch to the branch other.
1290
1330
 
 
1331
        This does not push or pull data between the branches, though it does
 
1332
        check for divergence to raise an error when the branches are not
 
1333
        either the same, or one a prefix of the other. That behaviour may not
 
1334
        be useful, so that check may be removed in future.
 
1335
        
1291
1336
        :param other: The branch to bind to
1292
1337
        :type other: Branch
1293
1338
        """
1298
1343
        #       but binding itself may not be.
1299
1344
        #       Since we *have* to check at commit time, we don't
1300
1345
        #       *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
 
 
 
1346
 
 
1347
        # we want to raise diverged if:
 
1348
        # last_rev is not in the other_last_rev history, AND
 
1349
        # other_last_rev is not in our history, and do it without pulling
 
1350
        # history around
 
1351
        last_rev = self.last_revision()
 
1352
        if last_rev is not None:
 
1353
            other.lock_read()
 
1354
            try:
 
1355
                other_last_rev = other.last_revision()
 
1356
                if other_last_rev is not None:
 
1357
                    # neither branch is new, we have to do some work to
 
1358
                    # ascertain diversion.
 
1359
                    remote_graph = other.repository.get_revision_graph(
 
1360
                        other_last_rev)
 
1361
                    local_graph = self.repository.get_revision_graph(last_rev)
 
1362
                    if (last_rev not in remote_graph and
 
1363
                        other_last_rev not in local_graph):
 
1364
                        raise errors.DivergedBranches(self, other)
 
1365
            finally:
 
1366
                other.unlock()
1330
1367
        self.set_bound_location(other.base)
1331
1368
 
1332
1369
    @needs_write_lock