~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2006-09-10 20:59:01 UTC
  • mto: This revision was merged to the branch mainline in revision 2004.
  • Revision ID: john@arbash-meinel.com-20060910205901-ceb5929c1497f81f
start working on some lazy importing code

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
24
20
from unittest import TestSuite
25
21
from warnings import warn
26
22
 
27
23
import bzrlib
28
 
import bzrlib.bzrdir as bzrdir
 
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
        )
29
36
from bzrlib.config import TreeConfig
30
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
 
from bzrlib.delta import compare_trees
32
38
import bzrlib.errors as errors
33
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
 
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
 
                           DivergedBranches, LockError,
36
 
                           UninitializableFormat,
37
 
                           UnlistableStore,
38
 
                           UnlistableBranch, NoSuchFile, NotVersionedError,
39
 
                           NoWorkingTree)
40
 
import bzrlib.inventory as inventory
41
 
from bzrlib.inventory import Inventory
 
39
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
 
40
                           HistoryMissing, InvalidRevisionId, 
 
41
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
42
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
 
43
                           NotBranchError, UninitializableFormat, 
 
44
                           UnlistableStore, UnlistableBranch, 
 
45
                           )
42
46
from bzrlib.lockable_files import LockableFiles, TransportLock
43
 
from bzrlib.lockdir import LockDir
44
 
from bzrlib.osutils import (isdir, quotefn,
45
 
                            rename, splitpath, sha_file,
46
 
                            file_kind, abspath, normpath, pathjoin,
47
 
                            safe_unicode,
48
 
                            rmtree,
49
 
                            )
50
 
from bzrlib.textui import show_status
 
47
from bzrlib.symbol_versioning import (deprecated_function,
 
48
                                      deprecated_method,
 
49
                                      DEPRECATED_PARAMETER,
 
50
                                      deprecated_passed,
 
51
                                      zero_eight, zero_nine,
 
52
                                      )
51
53
from bzrlib.trace import mutter, note
52
 
from bzrlib.tree import EmptyTree, RevisionTree
53
 
from bzrlib.repository import Repository
54
 
from bzrlib.revision import (
55
 
                             is_ancestor,
56
 
                             NULL_REVISION,
57
 
                             Revision,
58
 
                             )
59
 
from bzrlib.store import copy_all
60
 
from bzrlib.symbol_versioning import *
61
 
import bzrlib.transactions as transactions
62
 
from bzrlib.transport import Transport, get_transport
63
 
from bzrlib.tree import EmptyTree, RevisionTree
64
 
import bzrlib.ui
65
 
import bzrlib.xml5
66
54
 
67
55
 
68
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
117
105
        
118
106
    @staticmethod
119
107
    def open(base, _unsupported=False):
120
 
        """Open the repository rooted at base.
 
108
        """Open the branch rooted at base.
121
109
 
122
 
        For instance, if the repository is at URL/.bzr/repository,
123
 
        Repository.open(URL) -> a Repository instance.
 
110
        For instance, if the branch is at URL/.bzr/branch,
 
111
        Branch.open(URL) -> a Branch instance.
124
112
        """
125
113
        control = bzrdir.BzrDir.open(base, _unsupported)
126
114
        return control.open_branch(_unsupported)
150
138
        """
151
139
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
152
140
 
 
141
    @deprecated_function(zero_eight)
153
142
    def setup_caching(self, cache_root):
154
143
        """Subclasses that care about caching should override this, and set
155
144
        up cached stores located under cache_root.
 
145
        
 
146
        NOTE: This is unused.
156
147
        """
157
 
        # seems to be unused, 2006-01-13 mbp
158
 
        warn('%s is deprecated' % self.setup_caching)
159
 
        self.cache_root = cache_root
 
148
        pass
 
149
 
 
150
    def get_config(self):
 
151
        return bzrlib.config.BranchConfig(self)
160
152
 
161
153
    def _get_nick(self):
162
 
        cfg = self.tree_config()
163
 
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
 
154
        return self.get_config().get_nickname()
164
155
 
165
156
    def _set_nick(self, nick):
166
 
        cfg = self.tree_config()
167
 
        cfg.set_option(nick, "nickname")
168
 
        assert cfg.get_option("nickname") == nick
 
157
        self.get_config().set_user_option('nickname', nick)
169
158
 
170
159
    nick = property(_get_nick, _set_nick)
171
160
 
172
161
    def is_locked(self):
173
 
        raise NotImplementedError('is_locked is abstract')
 
162
        raise NotImplementedError(self.is_locked)
174
163
 
175
164
    def lock_write(self):
176
 
        raise NotImplementedError('lock_write is abstract')
 
165
        raise NotImplementedError(self.lock_write)
177
166
 
178
167
    def lock_read(self):
179
 
        raise NotImplementedError('lock_read is abstract')
 
168
        raise NotImplementedError(self.lock_read)
180
169
 
181
170
    def unlock(self):
182
 
        raise NotImplementedError('unlock is abstract')
 
171
        raise NotImplementedError(self.unlock)
183
172
 
184
173
    def peek_lock_mode(self):
185
174
        """Return lock mode for the Branch: 'r', 'w' or None"""
186
175
        raise NotImplementedError(self.peek_lock_mode)
187
176
 
188
177
    def get_physical_lock_status(self):
189
 
        raise NotImplementedError('get_physical_lock_status is abstract')
 
178
        raise NotImplementedError(self.get_physical_lock_status)
190
179
 
191
180
    def abspath(self, name):
192
181
        """Return absolute filename for something in the branch
194
183
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
195
184
        method and not a tree method.
196
185
        """
197
 
        raise NotImplementedError('abspath is abstract')
 
186
        raise NotImplementedError(self.abspath)
198
187
 
199
188
    def bind(self, other):
200
189
        """Bind the local branch the other branch.
219
208
        if self.base == from_branch.base:
220
209
            return (0, [])
221
210
        if pb is None:
222
 
            nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
211
            nested_pb = ui.ui_factory.nested_progress_bar()
223
212
            pb = nested_pb
224
213
        else:
225
214
            nested_pb = None
233
222
                    last_revision = from_history[-1]
234
223
                else:
235
224
                    # no history in the source branch
236
 
                    last_revision = NULL_REVISION
 
225
                    last_revision = revision.NULL_REVISION
237
226
            return self.repository.fetch(from_branch.repository,
238
227
                                         revision_id=last_revision,
239
228
                                         pb=nested_pb)
249
238
        branch.
250
239
        """
251
240
        return None
 
241
    
 
242
    def get_commit_builder(self, parents, config=None, timestamp=None, 
 
243
                           timezone=None, committer=None, revprops=None, 
 
244
                           revision_id=None):
 
245
        """Obtain a CommitBuilder for this branch.
 
246
        
 
247
        :param parents: Revision ids of the parents of the new revision.
 
248
        :param config: Optional configuration to use.
 
249
        :param timestamp: Optional timestamp recorded for commit.
 
250
        :param timezone: Optional timezone for timestamp.
 
251
        :param committer: Optional committer to set for commit.
 
252
        :param revprops: Optional dictionary of revision properties.
 
253
        :param revision_id: Optional revision id.
 
254
        """
 
255
 
 
256
        if config is None:
 
257
            config = self.get_config()
 
258
        
 
259
        return self.repository.get_commit_builder(self, parents, config, 
 
260
            timestamp, timezone, committer, revprops, revision_id)
252
261
 
253
262
    def get_master_branch(self):
254
263
        """Return the branch we are bound to.
257
266
        """
258
267
        return None
259
268
 
 
269
    def get_revision_delta(self, revno):
 
270
        """Return the delta for one revision.
 
271
 
 
272
        The delta is relative to its mainline predecessor, or the
 
273
        empty tree for revision 1.
 
274
        """
 
275
        assert isinstance(revno, int)
 
276
        rh = self.revision_history()
 
277
        if not (1 <= revno <= len(rh)):
 
278
            raise InvalidRevisionNumber(revno)
 
279
        return self.repository.get_revision_delta(rh[revno-1])
 
280
 
260
281
    def get_root_id(self):
261
282
        """Return the id of this branches root"""
262
 
        raise NotImplementedError('get_root_id is abstract')
 
283
        raise NotImplementedError(self.get_root_id)
263
284
 
264
285
    def print_file(self, file, revision_id):
265
286
        """Print `file` to stdout."""
266
 
        raise NotImplementedError('print_file is abstract')
 
287
        raise NotImplementedError(self.print_file)
267
288
 
268
289
    def append_revision(self, *revision_ids):
269
 
        raise NotImplementedError('append_revision is abstract')
 
290
        raise NotImplementedError(self.append_revision)
270
291
 
271
292
    def set_revision_history(self, rev_history):
272
 
        raise NotImplementedError('set_revision_history is abstract')
 
293
        raise NotImplementedError(self.set_revision_history)
273
294
 
274
295
    def revision_history(self):
275
296
        """Return sequence of revision hashes on to this branch."""
288
309
        raise errors.UpgradeRequired(self.base)
289
310
 
290
311
    def last_revision(self):
291
 
        """Return last patch hash, or None if no history."""
 
312
        """Return last revision id, or None"""
292
313
        ph = self.revision_history()
293
314
        if ph:
294
315
            return ph[-1]
300
321
        
301
322
        If self and other have not diverged, return a list of the revisions
302
323
        present in other, but missing from self.
303
 
 
304
 
        >>> from bzrlib.workingtree import WorkingTree
305
 
        >>> bzrlib.trace.silent = True
306
 
        >>> d1 = bzrdir.ScratchDir()
307
 
        >>> br1 = d1.open_branch()
308
 
        >>> wt1 = d1.open_workingtree()
309
 
        >>> d2 = bzrdir.ScratchDir()
310
 
        >>> br2 = d2.open_branch()
311
 
        >>> wt2 = d2.open_workingtree()
312
 
        >>> br1.missing_revisions(br2)
313
 
        []
314
 
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
315
 
        >>> br1.missing_revisions(br2)
316
 
        [u'REVISION-ID-1']
317
 
        >>> br2.missing_revisions(br1)
318
 
        []
319
 
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
320
 
        >>> br1.missing_revisions(br2)
321
 
        []
322
 
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
323
 
        >>> br1.missing_revisions(br2)
324
 
        [u'REVISION-ID-2A']
325
 
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
326
 
        >>> br1.missing_revisions(br2)
327
 
        Traceback (most recent call last):
328
 
        DivergedBranches: These branches have diverged.  Try merge.
329
324
        """
330
325
        self_history = self.revision_history()
331
326
        self_len = len(self_history)
341
336
        else:
342
337
            assert isinstance(stop_revision, int)
343
338
            if stop_revision > other_len:
344
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
339
                raise errors.NoSuchRevision(self, stop_revision)
345
340
        return other_history[self_len:stop_revision]
346
341
 
347
342
    def update_revisions(self, other, stop_revision=None):
351
346
        :param stop_revision: Updated until the given revision
352
347
        :return: None
353
348
        """
354
 
        raise NotImplementedError('update_revisions is abstract')
 
349
        raise NotImplementedError(self.update_revisions)
355
350
 
356
351
    def revision_id_to_revno(self, revision_id):
357
352
        """Given a revision id, return its revno"""
369
364
            return None
370
365
        if history is None:
371
366
            history = self.revision_history()
372
 
        elif revno <= 0 or revno > len(history):
 
367
        if revno <= 0 or revno > len(history):
373
368
            raise bzrlib.errors.NoSuchRevision(self, revno)
374
369
        return history[revno - 1]
375
370
 
376
371
    def pull(self, source, overwrite=False, stop_revision=None):
377
 
        raise NotImplementedError('pull is abstract')
 
372
        raise NotImplementedError(self.pull)
378
373
 
379
374
    def basis_tree(self):
380
 
        """Return `Tree` object for last revision.
381
 
 
382
 
        If there are no revisions yet, return an `EmptyTree`.
383
 
        """
 
375
        """Return `Tree` object for last revision."""
384
376
        return self.repository.revision_tree(self.last_revision())
385
377
 
386
378
    def rename_one(self, from_rel, to_rel):
388
380
 
389
381
        This can change the directory or the filename or both.
390
382
        """
391
 
        raise NotImplementedError('rename_one is abstract')
 
383
        raise NotImplementedError(self.rename_one)
392
384
 
393
385
    def move(self, from_paths, to_name):
394
386
        """Rename files.
404
396
        This returns a list of (from_path, to_path) pairs for each
405
397
        entry that is moved.
406
398
        """
407
 
        raise NotImplementedError('move is abstract')
 
399
        raise NotImplementedError(self.move)
408
400
 
409
401
    def get_parent(self):
410
402
        """Return the parent location of the branch.
413
405
        pattern is that the user can override it by specifying a
414
406
        location.
415
407
        """
416
 
        raise NotImplementedError('get_parent is abstract')
 
408
        raise NotImplementedError(self.get_parent)
 
409
 
 
410
    def get_submit_branch(self):
 
411
        """Return the submit location of the branch.
 
412
 
 
413
        This is the default location for bundle.  The usual
 
414
        pattern is that the user can override it by specifying a
 
415
        location.
 
416
        """
 
417
        return self.get_config().get_user_option('submit_branch')
 
418
 
 
419
    def set_submit_branch(self, location):
 
420
        """Return the submit location of the branch.
 
421
 
 
422
        This is the default location for bundle.  The usual
 
423
        pattern is that the user can override it by specifying a
 
424
        location.
 
425
        """
 
426
        self.get_config().set_user_option('submit_branch', location)
417
427
 
418
428
    def get_push_location(self):
419
429
        """Return the None or the location to push this branch to."""
420
 
        raise NotImplementedError('get_push_location is abstract')
 
430
        raise NotImplementedError(self.get_push_location)
421
431
 
422
432
    def set_push_location(self, location):
423
433
        """Set a new push location for this branch."""
424
 
        raise NotImplementedError('set_push_location is abstract')
 
434
        raise NotImplementedError(self.set_push_location)
425
435
 
426
436
    def set_parent(self, url):
427
 
        raise NotImplementedError('set_parent is abstract')
 
437
        raise NotImplementedError(self.set_parent)
428
438
 
429
439
    @needs_write_lock
430
440
    def update(self):
457
467
        revision_id: if not None, the revision history in the new branch will
458
468
                     be truncated to end with revision_id.
459
469
        """
460
 
        # for API compatability, until 0.8 releases we provide the old api:
 
470
        # for API compatibility, until 0.8 releases we provide the old api:
461
471
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
462
472
        # after 0.8 releases, the *args and **kwargs should be changed:
463
473
        # def clone(self, to_bzrdir, revision_id=None):
465
475
            kwargs.get('revision', None) or
466
476
            kwargs.get('basis_branch', None) or
467
477
            (len(args) and isinstance(args[0], basestring))):
468
 
            # backwards compatability api:
 
478
            # backwards compatibility api:
469
479
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
470
480
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
471
481
            # get basis_branch
533
543
                rev = self.repository.get_revision(revision_id)
534
544
                new_history = rev.get_history(self.repository)[1:]
535
545
        destination.set_revision_history(new_history)
536
 
        parent = self.get_parent()
537
 
        if parent:
538
 
            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)
 
553
 
 
554
    @needs_read_lock
 
555
    def check(self):
 
556
        """Check consistency of the branch.
 
557
 
 
558
        In particular this checks that revisions given in the revision-history
 
559
        do actually match up in the revision graph, and that they're all 
 
560
        present in the repository.
 
561
        
 
562
        Callers will typically also want to check the repository.
 
563
 
 
564
        :return: A BranchCheckResult.
 
565
        """
 
566
        mainline_parent_id = None
 
567
        for revision_id in self.revision_history():
 
568
            try:
 
569
                revision = self.repository.get_revision(revision_id)
 
570
            except errors.NoSuchRevision, e:
 
571
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
 
572
                            % revision_id)
 
573
            # In general the first entry on the revision history has no parents.
 
574
            # But it's not illegal for it to have parents listed; this can happen
 
575
            # in imports from Arch when the parents weren't reachable.
 
576
            if mainline_parent_id is not None:
 
577
                if mainline_parent_id not in revision.parent_ids:
 
578
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
 
579
                                        "parents of {%s}"
 
580
                                        % (mainline_parent_id, revision_id))
 
581
            mainline_parent_id = revision_id
 
582
        return BranchCheckResult(self)
 
583
 
 
584
    def create_checkout(self, to_location, revision_id=None, 
 
585
                        lightweight=False):
 
586
        """Create a checkout of a branch.
 
587
        
 
588
        :param to_location: The url to produce the checkout at
 
589
        :param revision_id: The revision to check out
 
590
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
591
        produce a bound branch (heavyweight checkout)
 
592
        :return: The tree of the created checkout
 
593
        """
 
594
        if lightweight:
 
595
            t = transport.get_transport(to_location)
 
596
            try:
 
597
                t.mkdir('.')
 
598
            except errors.FileExists:
 
599
                pass
 
600
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
601
            BranchReferenceFormat().initialize(checkout, self)
 
602
        else:
 
603
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
604
                to_location, force_new_tree=False)
 
605
            checkout = checkout_branch.bzrdir
 
606
            checkout_branch.bind(self)
 
607
            if revision_id is not None:
 
608
                rh = checkout_branch.revision_history()
 
609
                new_rh = rh[:rh.index(revision_id) + 1]
 
610
                checkout_branch.set_revision_history(new_rh)
 
611
        return checkout.create_workingtree(revision_id)
539
612
 
540
613
 
541
614
class BranchFormat(object):
572
645
        except NoSuchFile:
573
646
            raise NotBranchError(path=transport.base)
574
647
        except KeyError:
575
 
            raise errors.UnknownFormatError(format_string)
 
648
            raise errors.UnknownFormatError(format=format_string)
576
649
 
577
650
    @classmethod
578
651
    def get_default_format(klass):
589
662
 
590
663
    def initialize(self, a_bzrdir):
591
664
        """Create a branch of this format in a_bzrdir."""
592
 
        raise NotImplementedError(self.initialized)
 
665
        raise NotImplementedError(self.initialize)
593
666
 
594
667
    def is_supported(self):
595
668
        """Is this format supported?
705
778
        utf8_files = [('revision-history', ''),
706
779
                      ('branch-name', ''),
707
780
                      ]
708
 
        control_files = LockableFiles(branch_transport, 'lock', LockDir)
 
781
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
709
782
        control_files.create_lock()
710
783
        control_files.lock_write()
711
784
        control_files.put_utf8('format', self.get_format_string())
730
803
            format = BranchFormat.find_format(a_bzrdir)
731
804
            assert format.__class__ == self.__class__
732
805
        transport = a_bzrdir.get_branch_transport(None)
733
 
        control_files = LockableFiles(transport, 'lock', LockDir)
 
806
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
734
807
        return BzrBranch5(_format=self,
735
808
                          _control_files=control_files,
736
809
                          a_bzrdir=a_bzrdir,
767
840
            raise errors.UninitializableFormat(self)
768
841
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
769
842
        branch_transport = a_bzrdir.get_branch_transport(self)
770
 
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
771
 
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
772
 
        branch_transport.put('format', StringIO(self.get_format_string()))
 
843
        branch_transport.put_bytes('location',
 
844
            target_branch.bzrdir.root_transport.base)
 
845
        branch_transport.put_bytes('format', self.get_format_string())
773
846
        return self.open(a_bzrdir, _found=True)
774
847
 
775
848
    def __init__(self):
851
924
        self._base = self._transport.base
852
925
        self._format = _format
853
926
        if _control_files is None:
854
 
            raise BzrBadParameterMissing('_control_files')
 
927
            raise ValueError('BzrBranch _control_files is None')
855
928
        self.control_files = _control_files
856
929
        if deprecated_passed(init):
857
930
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
872
945
                 stacklevel=2)
873
946
            if (not relax_version_check
874
947
                and not self._format.is_supported()):
875
 
                raise errors.UnsupportedFormatError(
876
 
                        'sorry, branch format %r not supported' % fmt,
877
 
                        ['use a different bzr version',
878
 
                         'or remove the .bzr directory'
879
 
                         ' and "bzr init" again'])
 
948
                raise errors.UnsupportedFormatError(format=fmt)
880
949
        if deprecated_passed(transport):
881
950
            warn("BzrBranch.__init__(transport=XXX...): The transport "
882
951
                 "parameter is deprecated as of bzr 0.8. "
890
959
 
891
960
    __repr__ = __str__
892
961
 
893
 
    def __del__(self):
894
 
        # TODO: It might be best to do this somewhere else,
895
 
        # but it is nice for a Branch object to automatically
896
 
        # cache it's information.
897
 
        # Alternatively, we could have the Transport objects cache requests
898
 
        # See the earlier discussion about how major objects (like Branch)
899
 
        # should never expect their __del__ function to run.
900
 
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
901
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
902
 
            try:
903
 
                rmtree(self.cache_root)
904
 
            except:
905
 
                pass
906
 
            self.cache_root = None
907
 
 
908
962
    def _get_base(self):
909
963
        return self._base
910
964
 
949
1003
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
950
1004
        """
951
1005
        if format is None:
952
 
            format = BzrBranchFormat.find_format(self.bzrdir)
 
1006
            format = BranchFormat.find_format(self.bzrdir)
953
1007
        self._format = format
954
1008
        mutter("got branch format %s", self._format)
955
1009
 
963
1017
        return self.control_files.is_locked()
964
1018
 
965
1019
    def lock_write(self):
966
 
        # TODO: test for failed two phase locks. This is known broken.
967
 
        self.control_files.lock_write()
968
1020
        self.repository.lock_write()
 
1021
        try:
 
1022
            self.control_files.lock_write()
 
1023
        except:
 
1024
            self.repository.unlock()
 
1025
            raise
969
1026
 
970
1027
    def lock_read(self):
971
 
        # TODO: test for failed two phase locks. This is known broken.
972
 
        self.control_files.lock_read()
973
1028
        self.repository.lock_read()
 
1029
        try:
 
1030
            self.control_files.lock_read()
 
1031
        except:
 
1032
            self.repository.unlock()
 
1033
            raise
974
1034
 
975
1035
    def unlock(self):
976
1036
        # TODO: test for failed two phase locks. This is known broken.
977
1037
        try:
978
 
            self.repository.unlock()
979
 
        finally:
980
1038
            self.control_files.unlock()
 
1039
        finally:
 
1040
            self.repository.unlock()
981
1041
        
982
1042
    def peek_lock_mode(self):
983
1043
        if self.control_files._lock_count == 0:
1021
1081
            # not really an object yet, and the transaction is for objects.
1022
1082
            # transaction.register_clean(history)
1023
1083
 
1024
 
    def get_revision_delta(self, revno):
1025
 
        """Return the delta for one revision.
1026
 
 
1027
 
        The delta is relative to its mainline predecessor, or the
1028
 
        empty tree for revision 1.
1029
 
        """
1030
 
        assert isinstance(revno, int)
1031
 
        rh = self.revision_history()
1032
 
        if not (1 <= revno <= len(rh)):
1033
 
            raise InvalidRevisionNumber(revno)
1034
 
 
1035
 
        # revno is 1-based; list is 0-based
1036
 
 
1037
 
        new_tree = self.repository.revision_tree(rh[revno-1])
1038
 
        if revno == 1:
1039
 
            old_tree = EmptyTree()
1040
 
        else:
1041
 
            old_tree = self.repository.revision_tree(rh[revno-2])
1042
 
        return compare_trees(old_tree, new_tree)
1043
 
 
1044
1084
    @needs_read_lock
1045
1085
    def revision_history(self):
1046
1086
        """See Branch.revision_history."""
1047
1087
        transaction = self.get_transaction()
1048
1088
        history = transaction.map.find_revision_history()
1049
1089
        if history is not None:
1050
 
            mutter("cache hit for revision-history in %s", self)
 
1090
            # mutter("cache hit for revision-history in %s", self)
1051
1091
            return list(history)
1052
 
        history = [l.rstrip('\r\n') for l in
1053
 
                self.control_files.get_utf8('revision-history').readlines()]
 
1092
        decode_utf8 = cache_utf8.decode
 
1093
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1094
                self.control_files.get('revision-history').readlines()]
1054
1095
        transaction.map.add_revision_history(history)
1055
1096
        # this call is disabled because revision_history is 
1056
1097
        # not really an object yet, and the transaction is for objects.
1058
1099
        return list(history)
1059
1100
 
1060
1101
    @needs_write_lock
 
1102
    def generate_revision_history(self, revision_id, last_rev=None, 
 
1103
        other_branch=None):
 
1104
        """Create a new revision history that will finish with revision_id.
 
1105
        
 
1106
        :param revision_id: the new tip to use.
 
1107
        :param last_rev: The previous last_revision. If not None, then this
 
1108
            must be a ancestory of revision_id, or DivergedBranches is raised.
 
1109
        :param other_branch: The other branch that DivergedBranches should
 
1110
            raise with respect to.
 
1111
        """
 
1112
        # stop_revision must be a descendant of last_revision
 
1113
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1114
        if last_rev is not None and last_rev not in stop_graph:
 
1115
            # our previous tip is not merged into stop_revision
 
1116
            raise errors.DivergedBranches(self, other_branch)
 
1117
        # make a new revision history from the graph
 
1118
        current_rev_id = revision_id
 
1119
        new_history = []
 
1120
        while current_rev_id not in (None, revision.NULL_REVISION):
 
1121
            new_history.append(current_rev_id)
 
1122
            current_rev_id_parents = stop_graph[current_rev_id]
 
1123
            try:
 
1124
                current_rev_id = current_rev_id_parents[0]
 
1125
            except IndexError:
 
1126
                current_rev_id = None
 
1127
        new_history.reverse()
 
1128
        self.set_revision_history(new_history)
 
1129
 
 
1130
    @needs_write_lock
1061
1131
    def update_revisions(self, other, stop_revision=None):
1062
1132
        """See Branch.update_revisions."""
1063
1133
        other.lock_read()
1077
1147
            if stop_revision in my_ancestry:
1078
1148
                # last_revision is a descendant of stop_revision
1079
1149
                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)
 
1150
            self.generate_revision_history(stop_revision, last_rev=last_rev,
 
1151
                other_branch=other)
1097
1152
        finally:
1098
1153
            other.unlock()
1099
1154
 
1104
1159
    @deprecated_method(zero_eight)
1105
1160
    def working_tree(self):
1106
1161
        """Create a Working tree object for this branch."""
1107
 
        from bzrlib.workingtree import WorkingTree
 
1162
 
1108
1163
        from bzrlib.transport.local import LocalTransport
1109
1164
        if (self.base.find('://') != -1 or 
1110
1165
            not isinstance(self._transport, LocalTransport)):
1131
1186
 
1132
1187
    def get_parent(self):
1133
1188
        """See Branch.get_parent."""
1134
 
        import errno
 
1189
 
1135
1190
        _locs = ['parent', 'pull', 'x-pull']
 
1191
        assert self.base[-1] == '/'
1136
1192
        for l in _locs:
1137
1193
            try:
1138
 
                return self.control_files.get_utf8(l).read().strip('\n')
 
1194
                parent = self.control_files.get(l).read().strip('\n')
1139
1195
            except NoSuchFile:
1140
 
                pass
 
1196
                continue
 
1197
            # This is an old-format absolute path to a local branch
 
1198
            # turn it into a url
 
1199
            if parent.startswith('/'):
 
1200
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1201
            try:
 
1202
                return urlutils.join(self.base[:-1], parent)
 
1203
            except errors.InvalidURLJoin, e:
 
1204
                raise errors.InaccessibleParent(parent, self.base)
1141
1205
        return None
1142
1206
 
1143
1207
    def get_push_location(self):
1144
1208
        """See Branch.get_push_location."""
1145
 
        config = bzrlib.config.BranchConfig(self)
1146
 
        push_loc = config.get_user_option('push_location')
 
1209
        push_loc = self.get_config().get_user_option('push_location')
1147
1210
        return push_loc
1148
1211
 
1149
1212
    def set_push_location(self, location):
1150
1213
        """See Branch.set_push_location."""
1151
 
        config = bzrlib.config.LocationConfig(self.base)
1152
 
        config.set_user_option('push_location', location)
 
1214
        self.get_config().set_user_option('push_location', location, 
 
1215
                                          local=True)
1153
1216
 
1154
1217
    @needs_write_lock
1155
1218
    def set_parent(self, url):
1162
1225
        if url is None:
1163
1226
            self.control_files._transport.delete('parent')
1164
1227
        else:
1165
 
            self.control_files.put_utf8('parent', url + '\n')
 
1228
            if isinstance(url, unicode):
 
1229
                try: 
 
1230
                    url = url.encode('ascii')
 
1231
                except UnicodeEncodeError:
 
1232
                    raise bzrlib.errors.InvalidURL(url,
 
1233
                        "Urls must be 7-bit ascii, "
 
1234
                        "use bzrlib.urlutils.escape")
 
1235
                    
 
1236
            url = urlutils.relative_url(self.base, url)
 
1237
            self.control_files.put('parent', StringIO(url + '\n'))
1166
1238
 
 
1239
    @deprecated_function(zero_nine)
1167
1240
    def tree_config(self):
 
1241
        """DEPRECATED; call get_config instead.  
 
1242
        TreeConfig has become part of BranchConfig."""
1168
1243
        return TreeConfig(self)
1169
1244
 
1170
1245
 
1210
1285
 
1211
1286
        This could memoise the branch, but if thats done
1212
1287
        it must be revalidated on each new lock.
1213
 
        So for now we just dont memoise it.
 
1288
        So for now we just don't memoise it.
1214
1289
        # RBC 20060304 review this decision.
1215
1290
        """
1216
1291
        bound_loc = self.get_bound_location()
1262
1337
        # There may be a different check you could do here
1263
1338
        # rather than actually trying to install revisions remotely.
1264
1339
        # TODO: capture an exception which indicates the remote branch
1265
 
        #       is not writeable. 
 
1340
        #       is not writable. 
1266
1341
        #       If it is up-to-date, this probably should not be a failure
1267
1342
        
1268
1343
        # lock other for write so the revision-history syncing cannot race
1334
1409
        return result
1335
1410
 
1336
1411
 
 
1412
class BranchCheckResult(object):
 
1413
    """Results of checking branch consistency.
 
1414
 
 
1415
    :see: Branch.check
 
1416
    """
 
1417
 
 
1418
    def __init__(self, branch):
 
1419
        self.branch = branch
 
1420
 
 
1421
    def report_results(self, verbose):
 
1422
        """Report the check results via trace.note.
 
1423
        
 
1424
        :param verbose: Requests more detailed display of what was checked,
 
1425
            if any.
 
1426
        """
 
1427
        note('checked branch %s format %s',
 
1428
             self.branch.base,
 
1429
             self.branch._format)
 
1430
 
 
1431
 
1337
1432
######################################################################
1338
1433
# predicates
1339
1434
 
1340
1435
 
1341
1436
@deprecated_function(zero_eight)
1342
 
def ScratchBranch(*args, **kwargs):
1343
 
    """See bzrlib.bzrdir.ScratchDir."""
1344
 
    d = ScratchDir(*args, **kwargs)
1345
 
    return d.open_branch()
1346
 
 
1347
 
 
1348
 
@deprecated_function(zero_eight)
1349
1437
def is_control_file(*args, **kwargs):
1350
1438
    """See bzrlib.workingtree.is_control_file."""
1351
1439
    return bzrlib.workingtree.is_control_file(*args, **kwargs)