~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Aaron Bentley
  • Date: 2006-05-30 15:53:33 UTC
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: abentley@panoramicfeedback.com-20060530155333-2fd164d1cdf2afc3
Move BadBundle error (and subclasses) to errors.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
#
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
20
directories.
21
21
"""
22
22
 
23
 
# TODO: remove unittest dependency; put that stuff inside the test suite
24
 
 
25
23
from copy import deepcopy
 
24
import os
26
25
from cStringIO import StringIO
27
 
import os
28
 
from stat import S_ISDIR
29
26
from unittest import TestSuite
30
27
 
31
28
import bzrlib
32
29
import bzrlib.errors as errors
33
30
from bzrlib.lockable_files import LockableFiles, TransportLock
34
31
from bzrlib.lockdir import LockDir
 
32
from bzrlib.osutils import safe_unicode
35
33
from bzrlib.osutils import (
36
34
                            abspath,
37
35
                            pathjoin,
42
40
from bzrlib.store.revision.text import TextRevisionStore
43
41
from bzrlib.store.text import TextStore
44
42
from bzrlib.store.versioned import WeaveStore
 
43
from bzrlib.symbol_versioning import *
45
44
from bzrlib.trace import mutter
46
45
from bzrlib.transactions import WriteTransaction
47
 
from bzrlib.transport import get_transport
 
46
from bzrlib.transport import get_transport, urlunescape
48
47
from bzrlib.transport.local import LocalTransport
49
 
import bzrlib.urlutils as urlutils
50
48
from bzrlib.weave import Weave
51
49
from bzrlib.xml4 import serializer_v4
52
 
import bzrlib.xml5
 
50
from bzrlib.xml5 import serializer_v5
53
51
 
54
52
 
55
53
class BzrDir(object):
94
92
        """
95
93
        if not allow_unsupported and not format.is_supported():
96
94
            # see open_downlevel to open legacy branches.
97
 
            raise errors.UnsupportedFormatError(format=format)
 
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'])
98
100
 
99
101
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
100
102
        """Clone this bzrdir and its contents to url verbatim.
171
173
                    basis_repo = None
172
174
        return basis_repo, basis_branch, basis_tree
173
175
 
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
 
        head, tail = urlutils.split(url)
178
 
        if tail and tail != '.':
179
 
            t = bzrlib.transport.get_transport(head)
 
177
        segments = url.split('/')
 
178
        if segments and segments[-1] not in ('', '.'):
 
179
            parent = '/'.join(segments[:-1])
 
180
            t = bzrlib.transport.get_transport(parent)
180
181
            try:
181
 
                t.mkdir(tail)
 
182
                t.mkdir(segments[-1])
182
183
            except errors.FileExists:
183
184
                pass
184
185
 
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
 
        head, tail = urlutils.split(base)
200
 
        if tail and tail != '.':
201
 
            t = bzrlib.transport.get_transport(head)
 
199
        segments = base.split('/')
 
200
        if segments and segments[-1] not in ('', '.'):
 
201
            parent = '/'.join(segments[:-1])
 
202
            t = bzrlib.transport.get_transport(parent)
202
203
            try:
203
 
                t.mkdir(tail)
 
204
                t.mkdir(segments[-1])
204
205
            except errors.FileExists:
205
206
                pass
206
207
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
301
302
        that child class desires.
302
303
        """
303
304
        bzrdir = BzrDir.create(base)
304
 
        return bzrdir.create_repository(shared)
 
305
        return bzrdir.create_repository()
305
306
 
306
307
    @staticmethod
307
308
    def create_standalone_workingtree(base):
342
343
            pass
343
344
        next_transport = self.root_transport.clone('..')
344
345
        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
351
350
                raise errors.NoRepositoryPresent(self)
352
 
            # does it have a repository ?
353
351
            try:
354
352
                repository = found_bzrdir.open_repository()
355
353
            except errors.NoRepositoryPresent:
356
354
                next_transport = found_bzrdir.root_transport.clone('..')
357
 
                if (found_bzrdir.root_transport.base == next_transport.base):
358
 
                    # top of the file system
359
 
                    break
360
 
                else:
361
 
                    continue
 
355
                continue
362
356
            if ((found_bzrdir.root_transport.base == 
363
357
                 self.root_transport.base) or repository.is_shared()):
364
358
                return repository
371
365
 
372
366
        Note that bzr dirs that do not support format strings will raise
373
367
        IncompatibleFormat if the branch format they are given has
374
 
        a format string, and vice versa.
 
368
        a format string, and vice verca.
375
369
 
376
370
        If branch_format is None, the transport is returned with no 
377
371
        checking. if it is not None, then the returned transport is
384
378
 
385
379
        Note that bzr dirs that do not support format strings will raise
386
380
        IncompatibleFormat if the repository format they are given has
387
 
        a format string, and vice versa.
 
381
        a format string, and vice verca.
388
382
 
389
383
        If repository_format is None, the transport is returned with no 
390
384
        checking. if it is not None, then the returned transport is
397
391
 
398
392
        Note that bzr dirs that do not support format strings will raise
399
393
        IncompatibleFormat if the workingtree format they are given has
400
 
        a format string, and vice versa.
 
394
        a format string, and vice verca.
401
395
 
402
396
        If workingtree_format is None, the transport is returned with no 
403
397
        checking. if it is not None, then the returned transport is
432
426
        # this might be better on the BzrDirFormat class because it refers to 
433
427
        # all the possible bzrdir disk formats. 
434
428
        # This method is tested via the workingtree is_control_filename tests- 
435
 
        # it was extracted from WorkingTree.is_control_filename. If the methods
 
429
        # it was extractd from WorkingTree.is_control_filename. If the methods
436
430
        # contract is extended beyond the current trivial  implementation please
437
431
        # add new tests for it to the appropriate place.
438
432
        return filename == '.bzr' or filename.startswith('.bzr/')
495
489
        If there is one and it is either an unrecognised format or an unsupported 
496
490
        format, UnknownFormatError or UnsupportedFormatError are raised.
497
491
        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.
501
492
        """
502
493
        # this gets the normalised url back. I.e. '.' -> the full path.
503
494
        url = a_transport.base
505
496
            try:
506
497
                format = BzrDirFormat.find_format(a_transport)
507
498
                BzrDir._check_supported(format, False)
508
 
                return format.open(a_transport), urlutils.unescape(a_transport.relpath(url))
 
499
                return format.open(a_transport), a_transport.relpath(url)
509
500
            except errors.NotBranchError, e:
510
 
                ## mutter('not a branch in: %r %s', a_transport.base, e)
511
 
                pass
 
501
                mutter('not a branch in: %r %s', a_transport.base, e)
512
502
            new_t = a_transport.clone('..')
513
503
            if new_t.base == a_transport.base:
514
504
                # reached the root, whatever that may be
605
595
            result.create_repository()
606
596
        elif source_repository is not None and result_repo is None:
607
597
            # have source, and want to make a new target repo
608
 
            # we don't clone the repo because that preserves attributes
 
598
            # we dont clone the repo because that preserves attributes
609
599
            # like is_shared(), and we have not yet implemented a 
610
600
            # repository sprout().
611
601
            result_repo = result.create_repository()
620
610
            source_branch.sprout(result, revision_id=revision_id)
621
611
        else:
622
612
            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
625
613
        if result_repo is None or result_repo.make_working_trees():
626
614
            result.create_workingtree()
627
615
        return result
943
931
    _formats = {}
944
932
    """The known formats."""
945
933
 
946
 
    _control_formats = []
947
 
    """The registered control formats - .bzr, ....
948
 
    
949
 
    This is a list of BzrDirFormat objects.
950
 
    """
951
 
 
952
934
    _lock_file_name = 'branch-lock'
953
935
 
954
936
    # _lock_class must be set in subclasses to the lock type, typ.
956
938
 
957
939
    @classmethod
958
940
    def find_format(klass, transport):
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."""
 
941
        """Return the format registered for URL."""
971
942
        try:
972
943
            format_string = transport.get(".bzr/branch-format").read()
 
944
            return klass._formats[format_string]
973
945
        except errors.NoSuchFile:
974
946
            raise errors.NotBranchError(path=transport.base)
975
 
 
976
 
        try:
977
 
            return klass._formats[format_string]
978
947
        except KeyError:
979
 
            raise errors.UnknownFormatError(format=format_string)
 
948
            raise errors.UnknownFormatError(format_string)
980
949
 
981
950
    @classmethod
982
951
    def get_default_format(klass):
997
966
        This returns a bzrlib.bzrdir.Converter object.
998
967
 
999
968
        This should return the best upgrader to step this format towards the
1000
 
        current default format. In the case of plugins we can/should provide
 
969
        current default format. In the case of plugins we can/shouold provide
1001
970
        some means for them to extend the range of returnable converters.
1002
971
 
1003
 
        :param format: Optional format to override the default format of the 
 
972
        :param format: Optional format to override the default foramt of the 
1004
973
                       library.
1005
974
        """
1006
975
        raise NotImplementedError(self.get_converter)
1015
984
 
1016
985
    def initialize_on_transport(self, transport):
1017
986
        """Initialize a new bzrdir in the base directory of a Transport."""
1018
 
        # Since we don't have a .bzr directory, inherit the
 
987
        # Since we don'transport have a .bzr directory, inherit the
1019
988
        # mode from the root directory
1020
989
        temp_control = LockableFiles(transport, '', TransportLock)
1021
990
        temp_control._transport.mkdir('.bzr',
1022
 
                                      # FIXME: RBC 20060121 don't peek under
 
991
                                      # FIXME: RBC 20060121 dont peek under
1023
992
                                      # the covers
1024
993
                                      mode=temp_control._dir_mode)
1025
994
        file_mode = temp_control._file_mode
1052
1021
        """
1053
1022
        return True
1054
1023
 
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
 
 
1074
1024
    def open(self, transport, _found=False):
1075
1025
        """Return an instance of this format for the dir transport points at.
1076
1026
        
1094
1044
        klass._formats[format.get_format_string()] = format
1095
1045
 
1096
1046
    @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
1108
1047
    def set_default_format(klass, format):
1109
1048
        klass._default_format = format
1110
1049
 
1116
1055
        assert klass._formats[format.get_format_string()] is format
1117
1056
        del klass._formats[format.get_format_string()]
1118
1057
 
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
 
 
1127
1058
 
1128
1059
class BzrDirFormat4(BzrDirFormat):
1129
1060
    """Bzr dir format 4.
1376
1307
        return result
1377
1308
 
1378
1309
 
 
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
 
1379
1376
class Converter(object):
1380
1377
    """Converts a disk format object from one format to another."""
1381
1378
 
1467
1464
 
1468
1465
    def _convert_working_inv(self):
1469
1466
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1470
 
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1467
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1471
1468
        # FIXME inventory is a working tree change.
1472
1469
        self.branch.control_files.put('inventory', new_inv_xml)
1473
1470
 
1541
1538
    def _load_updated_inventory(self, rev_id):
1542
1539
        assert rev_id in self.converted_revs
1543
1540
        inv_xml = self.inv_weave.get_text(rev_id)
1544
 
        inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
 
1541
        inv = serializer_v5.read_inventory_from_string(inv_xml)
1545
1542
        return inv
1546
1543
 
1547
1544
    def _convert_one_rev(self, rev_id):
1557
1554
    def _store_new_weave(self, rev, inv, present_parents):
1558
1555
        # the XML is now updated with text versions
1559
1556
        if __debug__:
1560
 
            entries = inv.iter_entries()
1561
 
            entries.next()
1562
 
            for path, ie in entries:
 
1557
            for file_id in inv:
 
1558
                ie = inv[file_id]
 
1559
                if ie.kind == 'root_directory':
 
1560
                    continue
1563
1561
                assert hasattr(ie, 'revision'), \
1564
1562
                    'no revision on {%s} in {%s}' % \
1565
1563
                    (file_id, rev.revision_id)
1566
 
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1564
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1567
1565
        new_inv_sha1 = sha_string(new_inv_xml)
1568
1566
        self.inv_weave.add_lines(rev.revision_id, 
1569
1567
                                 present_parents,
1578
1576
        mutter('converting texts of revision {%s}',
1579
1577
               rev_id)
1580
1578
        parent_invs = map(self._load_updated_inventory, present_parents)
1581
 
        entries = inv.iter_entries()
1582
 
        entries.next()
1583
 
        for path, ie in entries:
 
1579
        for file_id in inv:
 
1580
            ie = inv[file_id]
1584
1581
            self._convert_file_version(rev, ie, parent_invs)
1585
1582
 
1586
1583
    def _convert_file_version(self, rev, ie, parent_invs):
1589
1586
        The file needs to be added into the weave if it is a merge
1590
1587
        of >=2 parents or if it's changed from its parent.
1591
1588
        """
 
1589
        if ie.kind == 'root_directory':
 
1590
            return
1592
1591
        file_id = ie.file_id
1593
1592
        rev_id = rev.revision_id
1594
1593
        w = self.text_weaves.get(file_id)
1673
1672
            store_transport = self.bzrdir.transport.clone(store_name)
1674
1673
            store = TransportStore(store_transport, prefixed=True)
1675
1674
            for urlfilename in store_transport.list_dir('.'):
1676
 
                filename = urlutils.unescape(urlfilename)
 
1675
                filename = urlunescape(urlfilename)
1677
1676
                if (filename.endswith(".weave") or
1678
1677
                    filename.endswith(".gz") or
1679
1678
                    filename.endswith(".sig")):