~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Matthieu Moy
  • Date: 2006-07-08 19:32:30 UTC
  • mfrom: (1845 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1857.
  • Revision ID: Matthieu.Moy@imag.fr-20060708193230-3eb72d871471bd5b
merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
directories.
21
21
"""
22
22
 
 
23
# TODO: remove unittest dependency; put that stuff inside the test suite
 
24
 
23
25
from copy import deepcopy
 
26
from cStringIO import StringIO
24
27
import os
25
 
from cStringIO import StringIO
 
28
from stat import S_ISDIR
26
29
from unittest import TestSuite
27
30
 
28
31
import bzrlib
29
32
import bzrlib.errors as errors
30
33
from bzrlib.lockable_files import LockableFiles, TransportLock
31
34
from bzrlib.lockdir import LockDir
32
 
from bzrlib.osutils import safe_unicode
33
35
from bzrlib.osutils import (
34
36
                            abspath,
35
37
                            pathjoin,
40
42
from bzrlib.store.revision.text import TextRevisionStore
41
43
from bzrlib.store.text import TextStore
42
44
from bzrlib.store.versioned import WeaveStore
43
 
from bzrlib.symbol_versioning import *
44
45
from bzrlib.trace import mutter
45
46
from bzrlib.transactions import WriteTransaction
46
 
from bzrlib.transport import get_transport, urlunescape
 
47
from bzrlib.transport import get_transport
47
48
from bzrlib.transport.local import LocalTransport
 
49
import bzrlib.urlutils as urlutils
48
50
from bzrlib.weave import Weave
49
51
from bzrlib.xml4 import serializer_v4
50
 
from bzrlib.xml5 import serializer_v5
 
52
import bzrlib.xml5
51
53
 
52
54
 
53
55
class BzrDir(object):
92
94
        """
93
95
        if not allow_unsupported and not format.is_supported():
94
96
            # see open_downlevel to open legacy branches.
95
 
            raise errors.UnsupportedFormatError(
96
 
                    'sorry, format %s not supported' % format,
97
 
                    ['use a different bzr version',
98
 
                     'or remove the .bzr directory'
99
 
                     ' and "bzr init" again'])
 
97
            raise errors.UnsupportedFormatError(format=format)
100
98
 
101
99
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
102
100
        """Clone this bzrdir and its contents to url verbatim.
173
171
                    basis_repo = None
174
172
        return basis_repo, basis_branch, basis_tree
175
173
 
 
174
    # TODO: This should be given a Transport, and should chdir up; otherwise
 
175
    # this will open a new connection.
176
176
    def _make_tail(self, url):
177
 
        segments = url.split('/')
178
 
        if segments and segments[-1] not in ('', '.'):
179
 
            parent = '/'.join(segments[:-1])
180
 
            t = bzrlib.transport.get_transport(parent)
 
177
        head, tail = urlutils.split(url)
 
178
        if tail and tail != '.':
 
179
            t = bzrlib.transport.get_transport(head)
181
180
            try:
182
 
                t.mkdir(segments[-1])
 
181
                t.mkdir(tail)
183
182
            except errors.FileExists:
184
183
                pass
185
184
 
 
185
    # TODO: Should take a Transport
186
186
    @classmethod
187
187
    def create(cls, base):
188
188
        """Create a new BzrDir at the url 'base'.
196
196
        if cls is not BzrDir:
197
197
            raise AssertionError("BzrDir.create always creates the default format, "
198
198
                    "not one of %r" % cls)
199
 
        segments = base.split('/')
200
 
        if segments and segments[-1] not in ('', '.'):
201
 
            parent = '/'.join(segments[:-1])
202
 
            t = bzrlib.transport.get_transport(parent)
 
199
        head, tail = urlutils.split(base)
 
200
        if tail and tail != '.':
 
201
            t = bzrlib.transport.get_transport(head)
203
202
            try:
204
 
                t.mkdir(segments[-1])
 
203
                t.mkdir(tail)
205
204
            except errors.FileExists:
206
205
                pass
207
206
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
343
342
            pass
344
343
        next_transport = self.root_transport.clone('..')
345
344
        while True:
 
345
            # find the next containing bzrdir
346
346
            try:
347
347
                found_bzrdir = BzrDir.open_containing_from_transport(
348
348
                    next_transport)[0]
349
349
            except errors.NotBranchError:
 
350
                # none found
350
351
                raise errors.NoRepositoryPresent(self)
 
352
            # does it have a repository ?
351
353
            try:
352
354
                repository = found_bzrdir.open_repository()
353
355
            except errors.NoRepositoryPresent:
354
356
                next_transport = found_bzrdir.root_transport.clone('..')
355
 
                continue
 
357
                if (found_bzrdir.root_transport.base == next_transport.base):
 
358
                    # top of the file system
 
359
                    break
 
360
                else:
 
361
                    continue
356
362
            if ((found_bzrdir.root_transport.base == 
357
363
                 self.root_transport.base) or repository.is_shared()):
358
364
                return repository
365
371
 
366
372
        Note that bzr dirs that do not support format strings will raise
367
373
        IncompatibleFormat if the branch format they are given has
368
 
        a format string, and vice verca.
 
374
        a format string, and vice versa.
369
375
 
370
376
        If branch_format is None, the transport is returned with no 
371
377
        checking. if it is not None, then the returned transport is
378
384
 
379
385
        Note that bzr dirs that do not support format strings will raise
380
386
        IncompatibleFormat if the repository format they are given has
381
 
        a format string, and vice verca.
 
387
        a format string, and vice versa.
382
388
 
383
389
        If repository_format is None, the transport is returned with no 
384
390
        checking. if it is not None, then the returned transport is
391
397
 
392
398
        Note that bzr dirs that do not support format strings will raise
393
399
        IncompatibleFormat if the workingtree format they are given has
394
 
        a format string, and vice verca.
 
400
        a format string, and vice versa.
395
401
 
396
402
        If workingtree_format is None, the transport is returned with no 
397
403
        checking. if it is not None, then the returned transport is
426
432
        # this might be better on the BzrDirFormat class because it refers to 
427
433
        # all the possible bzrdir disk formats. 
428
434
        # This method is tested via the workingtree is_control_filename tests- 
429
 
        # it was extractd from WorkingTree.is_control_filename. If the methods
 
435
        # it was extracted from WorkingTree.is_control_filename. If the methods
430
436
        # contract is extended beyond the current trivial  implementation please
431
437
        # add new tests for it to the appropriate place.
432
438
        return filename == '.bzr' or filename.startswith('.bzr/')
489
495
        If there is one and it is either an unrecognised format or an unsupported 
490
496
        format, UnknownFormatError or UnsupportedFormatError are raised.
491
497
        If there is one, it is returned, along with the unused portion of url.
 
498
 
 
499
        :return: The BzrDir that contains the path, and a Unicode path 
 
500
                for the rest of the URL.
492
501
        """
493
502
        # this gets the normalised url back. I.e. '.' -> the full path.
494
503
        url = a_transport.base
496
505
            try:
497
506
                format = BzrDirFormat.find_format(a_transport)
498
507
                BzrDir._check_supported(format, False)
499
 
                return format.open(a_transport), a_transport.relpath(url)
 
508
                return format.open(a_transport), urlutils.unescape(a_transport.relpath(url))
500
509
            except errors.NotBranchError, e:
501
 
                mutter('not a branch in: %r %s', a_transport.base, e)
 
510
                ## mutter('not a branch in: %r %s', a_transport.base, e)
 
511
                pass
502
512
            new_t = a_transport.clone('..')
503
513
            if new_t.base == a_transport.base:
504
514
                # reached the root, whatever that may be
595
605
            result.create_repository()
596
606
        elif source_repository is not None and result_repo is None:
597
607
            # have source, and want to make a new target repo
598
 
            # we dont clone the repo because that preserves attributes
 
608
            # we don't clone the repo because that preserves attributes
599
609
            # like is_shared(), and we have not yet implemented a 
600
610
            # repository sprout().
601
611
            result_repo = result.create_repository()
610
620
            source_branch.sprout(result, revision_id=revision_id)
611
621
        else:
612
622
            result.create_branch()
 
623
        # TODO: jam 20060426 we probably need a test in here in the
 
624
        #       case that the newly sprouted branch is a remote one
613
625
        if result_repo is None or result_repo.make_working_trees():
614
626
            result.create_workingtree()
615
627
        return result
931
943
    _formats = {}
932
944
    """The known formats."""
933
945
 
 
946
    _control_formats = []
 
947
    """The registered control formats - .bzr, ....
 
948
    
 
949
    This is a list of BzrDirFormat objects.
 
950
    """
 
951
 
934
952
    _lock_file_name = 'branch-lock'
935
953
 
936
954
    # _lock_class must be set in subclasses to the lock type, typ.
938
956
 
939
957
    @classmethod
940
958
    def find_format(klass, transport):
941
 
        """Return the format registered for URL."""
 
959
        """Return the format present at transport."""
 
960
        for format in klass._control_formats:
 
961
            try:
 
962
                return format.probe_transport(transport)
 
963
            except errors.NotBranchError:
 
964
                # this format does not find a control dir here.
 
965
                pass
 
966
        raise errors.NotBranchError(path=transport.base)
 
967
 
 
968
    @classmethod
 
969
    def probe_transport(klass, transport):
 
970
        """Return the .bzrdir style transport present at URL."""
942
971
        try:
943
972
            format_string = transport.get(".bzr/branch-format").read()
 
973
        except errors.NoSuchFile:
 
974
            raise errors.NotBranchError(path=transport.base)
 
975
 
 
976
        try:
944
977
            return klass._formats[format_string]
945
 
        except errors.NoSuchFile:
946
 
            raise errors.NotBranchError(path=transport.base)
947
978
        except KeyError:
948
 
            raise errors.UnknownFormatError(format_string)
 
979
            raise errors.UnknownFormatError(format=format_string)
949
980
 
950
981
    @classmethod
951
982
    def get_default_format(klass):
966
997
        This returns a bzrlib.bzrdir.Converter object.
967
998
 
968
999
        This should return the best upgrader to step this format towards the
969
 
        current default format. In the case of plugins we can/shouold provide
 
1000
        current default format. In the case of plugins we can/should provide
970
1001
        some means for them to extend the range of returnable converters.
971
1002
 
972
 
        :param format: Optional format to override the default foramt of the 
 
1003
        :param format: Optional format to override the default format of the 
973
1004
                       library.
974
1005
        """
975
1006
        raise NotImplementedError(self.get_converter)
984
1015
 
985
1016
    def initialize_on_transport(self, transport):
986
1017
        """Initialize a new bzrdir in the base directory of a Transport."""
987
 
        # Since we don'transport have a .bzr directory, inherit the
 
1018
        # Since we don't have a .bzr directory, inherit the
988
1019
        # mode from the root directory
989
1020
        temp_control = LockableFiles(transport, '', TransportLock)
990
1021
        temp_control._transport.mkdir('.bzr',
991
 
                                      # FIXME: RBC 20060121 dont peek under
 
1022
                                      # FIXME: RBC 20060121 don't peek under
992
1023
                                      # the covers
993
1024
                                      mode=temp_control._dir_mode)
994
1025
        file_mode = temp_control._file_mode
1021
1052
        """
1022
1053
        return True
1023
1054
 
 
1055
    @classmethod
 
1056
    def known_formats(klass):
 
1057
        """Return all the known formats.
 
1058
        
 
1059
        Concrete formats should override _known_formats.
 
1060
        """
 
1061
        # There is double indirection here to make sure that control 
 
1062
        # formats used by more than one dir format will only be probed 
 
1063
        # once. This can otherwise be quite expensive for remote connections.
 
1064
        result = set()
 
1065
        for format in klass._control_formats:
 
1066
            result.update(format._known_formats())
 
1067
        return result
 
1068
    
 
1069
    @classmethod
 
1070
    def _known_formats(klass):
 
1071
        """Return the known format instances for this control format."""
 
1072
        return set(klass._formats.values())
 
1073
 
1024
1074
    def open(self, transport, _found=False):
1025
1075
        """Return an instance of this format for the dir transport points at.
1026
1076
        
1044
1094
        klass._formats[format.get_format_string()] = format
1045
1095
 
1046
1096
    @classmethod
 
1097
    def register_control_format(klass, format):
 
1098
        """Register a format that does not use '.bzrdir' for its control dir.
 
1099
 
 
1100
        TODO: This should be pulled up into a 'ControlDirFormat' base class
 
1101
        which BzrDirFormat can inherit from, and renamed to register_format 
 
1102
        there. It has been done without that for now for simplicity of
 
1103
        implementation.
 
1104
        """
 
1105
        klass._control_formats.append(format)
 
1106
 
 
1107
    @classmethod
1047
1108
    def set_default_format(klass, format):
1048
1109
        klass._default_format = format
1049
1110
 
1055
1116
        assert klass._formats[format.get_format_string()] is format
1056
1117
        del klass._formats[format.get_format_string()]
1057
1118
 
 
1119
    @classmethod
 
1120
    def unregister_control_format(klass, format):
 
1121
        klass._control_formats.remove(format)
 
1122
 
 
1123
 
 
1124
# register BzrDirFormat as a control format
 
1125
BzrDirFormat.register_control_format(BzrDirFormat)
 
1126
 
1058
1127
 
1059
1128
class BzrDirFormat4(BzrDirFormat):
1060
1129
    """Bzr dir format 4.
1307
1376
        return result
1308
1377
 
1309
1378
 
1310
 
class ScratchDir(BzrDir6):
1311
 
    """Special test class: a bzrdir that cleans up itself..
1312
 
 
1313
 
    >>> d = ScratchDir()
1314
 
    >>> base = d.transport.base
1315
 
    >>> isdir(base)
1316
 
    True
1317
 
    >>> b.transport.__del__()
1318
 
    >>> isdir(base)
1319
 
    False
1320
 
    """
1321
 
 
1322
 
    def __init__(self, files=[], dirs=[], transport=None):
1323
 
        """Make a test branch.
1324
 
 
1325
 
        This creates a temporary directory and runs init-tree in it.
1326
 
 
1327
 
        If any files are listed, they are created in the working copy.
1328
 
        """
1329
 
        if transport is None:
1330
 
            transport = bzrlib.transport.local.ScratchTransport()
1331
 
            # local import for scope restriction
1332
 
            BzrDirFormat6().initialize(transport.base)
1333
 
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1334
 
            self.create_repository()
1335
 
            self.create_branch()
1336
 
            self.create_workingtree()
1337
 
        else:
1338
 
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1339
 
 
1340
 
        # BzrBranch creates a clone to .bzr and then forgets about the
1341
 
        # original transport. A ScratchTransport() deletes itself and
1342
 
        # everything underneath it when it goes away, so we need to
1343
 
        # grab a local copy to prevent that from happening
1344
 
        self._transport = transport
1345
 
 
1346
 
        for d in dirs:
1347
 
            self._transport.mkdir(d)
1348
 
            
1349
 
        for f in files:
1350
 
            self._transport.put(f, 'content of %s' % f)
1351
 
 
1352
 
    def clone(self):
1353
 
        """
1354
 
        >>> orig = ScratchDir(files=["file1", "file2"])
1355
 
        >>> os.listdir(orig.base)
1356
 
        [u'.bzr', u'file1', u'file2']
1357
 
        >>> clone = orig.clone()
1358
 
        >>> if os.name != 'nt':
1359
 
        ...   os.path.samefile(orig.base, clone.base)
1360
 
        ... else:
1361
 
        ...   orig.base == clone.base
1362
 
        ...
1363
 
        False
1364
 
        >>> os.listdir(clone.base)
1365
 
        [u'.bzr', u'file1', u'file2']
1366
 
        """
1367
 
        from shutil import copytree
1368
 
        from bzrlib.osutils import mkdtemp
1369
 
        base = mkdtemp()
1370
 
        os.rmdir(base)
1371
 
        copytree(self.base, base, symlinks=True)
1372
 
        return ScratchDir(
1373
 
            transport=bzrlib.transport.local.ScratchTransport(base))
1374
 
 
1375
 
 
1376
1379
class Converter(object):
1377
1380
    """Converts a disk format object from one format to another."""
1378
1381
 
1464
1467
 
1465
1468
    def _convert_working_inv(self):
1466
1469
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1467
 
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
 
1470
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1468
1471
        # FIXME inventory is a working tree change.
1469
1472
        self.branch.control_files.put('inventory', new_inv_xml)
1470
1473
 
1538
1541
    def _load_updated_inventory(self, rev_id):
1539
1542
        assert rev_id in self.converted_revs
1540
1543
        inv_xml = self.inv_weave.get_text(rev_id)
1541
 
        inv = serializer_v5.read_inventory_from_string(inv_xml)
 
1544
        inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
1542
1545
        return inv
1543
1546
 
1544
1547
    def _convert_one_rev(self, rev_id):
1561
1564
                assert hasattr(ie, 'revision'), \
1562
1565
                    'no revision on {%s} in {%s}' % \
1563
1566
                    (file_id, rev.revision_id)
1564
 
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
 
1567
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1565
1568
        new_inv_sha1 = sha_string(new_inv_xml)
1566
1569
        self.inv_weave.add_lines(rev.revision_id, 
1567
1570
                                 present_parents,
1672
1675
            store_transport = self.bzrdir.transport.clone(store_name)
1673
1676
            store = TransportStore(store_transport, prefixed=True)
1674
1677
            for urlfilename in store_transport.list_dir('.'):
1675
 
                filename = urlunescape(urlfilename)
 
1678
                filename = urlutils.unescape(urlfilename)
1676
1679
                if (filename.endswith(".weave") or
1677
1680
                    filename.endswith(".gz") or
1678
1681
                    filename.endswith(".sig")):