/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

Branch now uses BzrDir reasonably sanely.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""BzrDir logic. The BzrDir is the basic control directory used by bzr.
 
18
 
 
19
At format 7 this was split out into Branch, Repository and Checkout control
 
20
directories.
 
21
"""
 
22
 
 
23
from copy import deepcopy
 
24
from cStringIO import StringIO
 
25
from unittest import TestSuite
 
26
 
 
27
 
 
28
import bzrlib
 
29
import bzrlib.errors as errors
 
30
from bzrlib.lockable_files import LockableFiles
 
31
from bzrlib.osutils import safe_unicode
 
32
from bzrlib.trace import mutter
 
33
from bzrlib.symbol_versioning import *
 
34
from bzrlib.transport import get_transport
 
35
 
 
36
 
 
37
class BzrDir(object):
 
38
    """A .bzr control diretory.
 
39
    
 
40
    BzrDir instances let you create or open any of the things that can be
 
41
    found within .bzr - checkouts, branches and repositories.
 
42
    
 
43
    transport
 
44
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
45
    """
 
46
 
 
47
    @staticmethod
 
48
    def create(base):
 
49
        """Create a new BzrDir at the url 'base'.
 
50
        
 
51
        This will call the current default formats initialize with base
 
52
        as the only parameter.
 
53
 
 
54
        If you need a specific format, consider creating an instance
 
55
        of that and calling initialize().
 
56
        """
 
57
        segments = base.split('/')
 
58
        if segments and segments[-1] not in ('', '.'):
 
59
            parent = '/'.join(segments[:-1])
 
60
            t = bzrlib.transport.get_transport(parent)
 
61
            try:
 
62
                t.mkdir(segments[-1])
 
63
            except errors.FileExists:
 
64
                pass
 
65
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
66
 
 
67
    def create_branch(self):
 
68
        """Create a branch in this BzrDir.
 
69
 
 
70
        The bzrdirs format will control what branch format is created.
 
71
        For more control see BranchFormatXX.create(a_bzrdir).
 
72
        """
 
73
        raise NotImplementedError(self.create_branch)
 
74
 
 
75
    @staticmethod
 
76
    def create_branch_and_repo(base):
 
77
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
78
 
 
79
        This will use the current default BzrDirFormat, and use whatever 
 
80
        repository format that that uses via bzrdir.create_branch and
 
81
        create_repository.
 
82
 
 
83
        The created Branch object is returned.
 
84
        """
 
85
        bzrdir = BzrDir.create(base)
 
86
        bzrdir.create_repository()
 
87
        return bzrdir.create_branch()
 
88
        
 
89
    @staticmethod
 
90
    def create_repository(base):
 
91
        """Create a new BzrDir and Repository at the url 'base'.
 
92
 
 
93
        This will use the current default BzrDirFormat, and use whatever 
 
94
        repository format that that uses for bzrdirformat.create_repository.
 
95
 
 
96
        The Repository object is returned.
 
97
 
 
98
        This must be overridden as an instance method in child classes, where
 
99
        it should take no parameters and construct whatever repository format
 
100
        that child class desires.
 
101
        """
 
102
        bzrdir = BzrDir.create(base)
 
103
        return bzrdir.create_repository()
 
104
 
 
105
    def __init__(self, _transport, _format):
 
106
        """Initialize a Bzr control dir object.
 
107
        
 
108
        Only really common logic should reside here, concrete classes should be
 
109
        made with varying behaviours.
 
110
 
 
111
        _format: the format that is creating this BzrDir instance.
 
112
        _transport: the transport this dir is based at.
 
113
        """
 
114
        self._format = _format
 
115
        self.transport = _transport.clone('.bzr')
 
116
 
 
117
    @staticmethod
 
118
    def open_unsupported(base):
 
119
        """Open a branch which is not supported."""
 
120
        return BzrDir.open(base, _unsupported=True)
 
121
        
 
122
    @staticmethod
 
123
    def open(base, _unsupported=False):
 
124
        """Open an existing branch, rooted at 'base' (url)
 
125
        
 
126
        _unsupported is a private parameter to the BzrDir class.
 
127
        """
 
128
        t = get_transport(base)
 
129
        mutter("trying to open %r with transport %r", base, t)
 
130
        format = BzrDirFormat.find_format(t)
 
131
        if not _unsupported and not format.is_supported():
 
132
            # see open_downlevel to open legacy branches.
 
133
            raise errors.UnsupportedFormatError(
 
134
                    'sorry, format %s not supported' % format,
 
135
                    ['use a different bzr version',
 
136
                     'or remove the .bzr directory'
 
137
                     ' and "bzr init" again'])
 
138
        return format.open(t, _found=True)
 
139
 
 
140
    def open_branch(self):
 
141
        """Open the branch object at this BzrDir if one is present.
 
142
        
 
143
        TODO: static convenience version of this?
 
144
        """
 
145
        raise NotImplementedError(self.open_branch)
 
146
 
 
147
    @staticmethod
 
148
    def open_containing(url):
 
149
        """Open an existing branch which contains url.
 
150
        
 
151
        This probes for a branch at url, and searches upwards from there.
 
152
 
 
153
        Basically we keep looking up until we find the control directory or
 
154
        run into the root.  If there isn't one, raises NotBranchError.
 
155
        If there is one and it is either an unrecognised format or an unsupported 
 
156
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
157
        If there is one, it is returned, along with the unused portion of url.
 
158
        """
 
159
        t = get_transport(url)
 
160
        # this gets the normalised url back. I.e. '.' -> the full path.
 
161
        url = t.base
 
162
        while True:
 
163
            try:
 
164
                format = BzrDirFormat.find_format(t)
 
165
                return format.open(t), t.relpath(url)
 
166
            except errors.NotBranchError, e:
 
167
                mutter('not a branch in: %r %s', t.base, e)
 
168
            new_t = t.clone('..')
 
169
            if new_t.base == t.base:
 
170
                # reached the root, whatever that may be
 
171
                raise errors.NotBranchError(path=url)
 
172
            t = new_t
 
173
 
 
174
    def open_repository(self):
 
175
        """Open the repository object at this BzrDir if one is present.
 
176
        
 
177
        TODO: static convenience version of this?
 
178
        TODO: NoRepositoryError that can be raised.
 
179
        """
 
180
        raise NotImplementedError(self.open_repository)
 
181
 
 
182
 
 
183
class BzrDir4(BzrDir):
 
184
    """A .bzr version 4 control object."""
 
185
 
 
186
    def create_branch(self):
 
187
        """See BzrDir.create_branch."""
 
188
        from bzrlib.branch import BzrBranchFormat4
 
189
        return BzrBranchFormat4().initialize(self)
 
190
 
 
191
    def create_repository(self):
 
192
        """See BzrDir.create_repository."""
 
193
        from bzrlib.repository import RepositoryFormat4
 
194
        return RepositoryFormat4().initialize(self)
 
195
 
 
196
    def open_branch(self):
 
197
        """See BzrDir.open_branch."""
 
198
        from bzrlib.branch import BzrBranchFormat4
 
199
        return BzrBranchFormat4().open(self, _found=True)
 
200
 
 
201
    def open_repository(self):
 
202
        """See BzrDir.open_repository."""
 
203
        from bzrlib.repository import RepositoryFormat4
 
204
        return RepositoryFormat4().open(self, _found=True)
 
205
 
 
206
 
 
207
class BzrDir5(BzrDir):
 
208
    """A .bzr version 5 control object."""
 
209
 
 
210
    def create_branch(self):
 
211
        """See BzrDir.create_branch."""
 
212
        from bzrlib.branch import BzrBranchFormat4
 
213
        return BzrBranchFormat4().initialize(self)
 
214
 
 
215
    def create_repository(self):
 
216
        """See BzrDir.create_repository."""
 
217
        from bzrlib.repository import RepositoryFormat5
 
218
        return RepositoryFormat5().initialize(self)
 
219
 
 
220
    def open_branch(self):
 
221
        """See BzrDir.open_branch."""
 
222
        from bzrlib.branch import BzrBranchFormat4
 
223
        return BzrBranchFormat4().open(self, _found=True)
 
224
 
 
225
    def open_repository(self):
 
226
        """See BzrDir.open_repository."""
 
227
        from bzrlib.repository import RepositoryFormat5
 
228
        return RepositoryFormat5().open(self, _found=True)
 
229
 
 
230
 
 
231
class BzrDir6(BzrDir):
 
232
    """A .bzr version 6 control object."""
 
233
 
 
234
    def create_branch(self):
 
235
        """See BzrDir.create_branch."""
 
236
        from bzrlib.branch import BzrBranchFormat4
 
237
        return BzrBranchFormat4().initialize(self)
 
238
 
 
239
    def create_repository(self):
 
240
        """See BzrDir.create_repository."""
 
241
        from bzrlib.repository import RepositoryFormat6
 
242
        return RepositoryFormat6().initialize(self)
 
243
 
 
244
    def open_branch(self):
 
245
        """See BzrDir.open_branch."""
 
246
        from bzrlib.branch import BzrBranchFormat4
 
247
        return BzrBranchFormat4().open(self, _found=True)
 
248
 
 
249
    def open_repository(self):
 
250
        """See BzrDir.open_repository."""
 
251
        from bzrlib.repository import RepositoryFormat6
 
252
        return RepositoryFormat6().open(self, _found=True)
 
253
 
 
254
 
 
255
class BzrDirFormat(object):
 
256
    """An encapsulation of the initialization and open routines for a format.
 
257
 
 
258
    Formats provide three things:
 
259
     * An initialization routine,
 
260
     * a format string,
 
261
     * an open routine.
 
262
 
 
263
    Formats are placed in an dict by their format string for reference 
 
264
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
265
    for consistency.
 
266
 
 
267
    Once a format is deprecated, just deprecate the initialize and open
 
268
    methods on the format class. Do not deprecate the object, as the 
 
269
    object will be created every system load.
 
270
    """
 
271
 
 
272
    _default_format = None
 
273
    """The default format used for new .bzr dirs."""
 
274
 
 
275
    _formats = {}
 
276
    """The known formats."""
 
277
 
 
278
    @classmethod
 
279
    def find_format(klass, transport):
 
280
        """Return the format registered for URL."""
 
281
        try:
 
282
            format_string = transport.get(".bzr/branch-format").read()
 
283
            return klass._formats[format_string]
 
284
        except errors.NoSuchFile:
 
285
            raise errors.NotBranchError(path=transport.base)
 
286
        except KeyError:
 
287
            raise errors.UnknownFormatError(format_string)
 
288
 
 
289
    @classmethod
 
290
    def get_default_format(klass):
 
291
        """Return the current default format."""
 
292
        return klass._default_format
 
293
 
 
294
    def get_format_string(self):
 
295
        """Return the ASCII format string that identifies this format."""
 
296
        raise NotImplementedError(self.get_format_string)
 
297
 
 
298
    def initialize(self, url):
 
299
        """Create a bzr control dir at this url and return an opened copy."""
 
300
        # Since we don't have a .bzr directory, inherit the
 
301
        # mode from the root directory
 
302
        t = get_transport(url)
 
303
        temp_control = LockableFiles(t, '')
 
304
        temp_control._transport.mkdir('.bzr',
 
305
                                      # FIXME: RBC 20060121 dont peek under
 
306
                                      # the covers
 
307
                                      mode=temp_control._dir_mode)
 
308
        file_mode = temp_control._file_mode
 
309
        del temp_control
 
310
        mutter('created control directory in ' + t.base)
 
311
        control = t.clone('.bzr')
 
312
        lock_file = 'branch-lock'
 
313
        utf8_files = [('README', 
 
314
                       "This is a Bazaar-NG control directory.\n"
 
315
                       "Do not change any files in this directory.\n"),
 
316
                      ('branch-format', self.get_format_string()),
 
317
                      ]
 
318
        # NB: no need to escape relative paths that are url safe.
 
319
        control.put(lock_file, StringIO(), mode=file_mode)
 
320
        control_files = LockableFiles(control, lock_file)
 
321
        control_files.lock_write()
 
322
        try:
 
323
            for file, content in utf8_files:
 
324
                control_files.put_utf8(file, content)
 
325
        finally:
 
326
            control_files.unlock()
 
327
        return self.open(t, _found=True)
 
328
 
 
329
    def is_supported(self):
 
330
        """Is this format supported?
 
331
 
 
332
        Supported formats must be initializable and openable.
 
333
        Unsupported formats may not support initialization or committing or 
 
334
        some other features depending on the reason for not being supported.
 
335
        """
 
336
        return True
 
337
 
 
338
    def open(self, transport, _found=False):
 
339
        """Return an instance of this format for the dir transport points at.
 
340
        
 
341
        _found is a private parameter, do not use it.
 
342
        """
 
343
        if not _found:
 
344
            assert isinstance(BzrDirFormat.find_format(transport),
 
345
                              self.__class__)
 
346
        return self._open(transport)
 
347
 
 
348
    def _open(self, transport):
 
349
        """Template method helper for opening BzrDirectories.
 
350
 
 
351
        This performs the actual open and any additional logic or parameter
 
352
        passing.
 
353
        """
 
354
        raise NotImplementedError(self._open)
 
355
 
 
356
    @classmethod
 
357
    def register_format(klass, format):
 
358
        klass._formats[format.get_format_string()] = format
 
359
 
 
360
    @classmethod
 
361
    def set_default_format(klass, format):
 
362
        klass._default_format = format
 
363
 
 
364
    @classmethod
 
365
    def unregister_format(klass, format):
 
366
        assert klass._formats[format.get_format_string()] is format
 
367
        del klass._formats[format.get_format_string()]
 
368
 
 
369
 
 
370
class BzrDirFormat4(BzrDirFormat):
 
371
    """Bzr dir format 4.
 
372
 
 
373
    This format is a combined format for working tree, branch and repository.
 
374
    It has:
 
375
     - Format 1 working trees
 
376
     - Format 4 branches
 
377
     - Format 4 repositories
 
378
 
 
379
    This format is deprecated: it indexes texts using a text it which is
 
380
    removed in format 5; write support for this format has been removed.
 
381
    """
 
382
 
 
383
    def get_format_string(self):
 
384
        """See BzrDirFormat.get_format_string()."""
 
385
        return "Bazaar-NG branch, format 0.0.4\n"
 
386
 
 
387
    def initialize(self, url):
 
388
        """Format 4 branches cannot be created."""
 
389
        raise errors.UninitializableFormat(self)
 
390
 
 
391
    def is_supported(self):
 
392
        """Format 4 is not supported.
 
393
 
 
394
        It is not supported because the model changed from 4 to 5 and the
 
395
        conversion logic is expensive - so doing it on the fly was not 
 
396
        feasible.
 
397
        """
 
398
        return False
 
399
 
 
400
    def _open(self, transport):
 
401
        """See BzrDirFormat._open."""
 
402
        return BzrDir4(transport, self)
 
403
 
 
404
 
 
405
class BzrDirFormat5(BzrDirFormat):
 
406
    """Bzr control format 5.
 
407
 
 
408
    This format is a combined format for working tree, branch and repository.
 
409
    It has:
 
410
     - Format 2 working trees
 
411
     - Format 4 branches
 
412
     - Format 6 repositories
 
413
    """
 
414
 
 
415
    def get_format_string(self):
 
416
        """See BzrDirFormat.get_format_string()."""
 
417
        return "Bazaar-NG branch, format 5\n"
 
418
 
 
419
    def _open(self, transport):
 
420
        """See BzrDirFormat._open."""
 
421
        return BzrDir5(transport, self)
 
422
 
 
423
 
 
424
class BzrDirFormat6(BzrDirFormat):
 
425
    """Bzr control format 6.
 
426
 
 
427
    This format is a combined format for working tree, branch and repository.
 
428
    It has:
 
429
     - Format 2 working trees
 
430
     - Format 4 branches
 
431
     - Format 6 repositories
 
432
    """
 
433
 
 
434
    def get_format_string(self):
 
435
        """See BzrDirFormat.get_format_string()."""
 
436
        return "Bazaar-NG branch, format 6\n"
 
437
 
 
438
    def _open(self, transport):
 
439
        """See BzrDirFormat._open."""
 
440
        return BzrDir6(transport, self)
 
441
 
 
442
 
 
443
BzrDirFormat.register_format(BzrDirFormat4())
 
444
BzrDirFormat.register_format(BzrDirFormat5())
 
445
__default_format = BzrDirFormat6()
 
446
BzrDirFormat.register_format(__default_format)
 
447
BzrDirFormat.set_default_format(__default_format)
 
448
 
 
449
 
 
450
class BzrDirTestProviderAdapter(object):
 
451
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
452
 
 
453
    This is done by copying the test once for each transport and injecting
 
454
    the transport_server, transport_readonly_server, and bzrdir_format
 
455
    classes into each copy. Each copy is also given a new id() to make it
 
456
    easy to identify.
 
457
    """
 
458
 
 
459
    def __init__(self, transport_server, transport_readonly_server, formats):
 
460
        self._transport_server = transport_server
 
461
        self._transport_readonly_server = transport_readonly_server
 
462
        self._formats = formats
 
463
    
 
464
    def adapt(self, test):
 
465
        result = TestSuite()
 
466
        for format in self._formats:
 
467
            new_test = deepcopy(test)
 
468
            new_test.transport_server = self._transport_server
 
469
            new_test.transport_readonly_server = self._transport_readonly_server
 
470
            new_test.bzrdir_format = format
 
471
            def make_new_test_id():
 
472
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
473
                return lambda: new_id
 
474
            new_test.id = make_new_test_id()
 
475
            result.addTest(new_test)
 
476
        return result
 
477
 
 
478
 
 
479
class ScratchDir(BzrDir6):
 
480
    """Special test class: a bzrdir that cleans up itself..
 
481
 
 
482
    >>> d = ScratchDir()
 
483
    >>> base = d.transport.base
 
484
    >>> isdir(base)
 
485
    True
 
486
    >>> b.transport.__del__()
 
487
    >>> isdir(base)
 
488
    False
 
489
    """
 
490
 
 
491
    def __init__(self, files=[], dirs=[], transport=None):
 
492
        """Make a test branch.
 
493
 
 
494
        This creates a temporary directory and runs init-tree in it.
 
495
 
 
496
        If any files are listed, they are created in the working copy.
 
497
        """
 
498
        if transport is None:
 
499
            transport = bzrlib.transport.local.ScratchTransport()
 
500
            # local import for scope restriction
 
501
            BzrDirFormat6().initialize(transport.base)
 
502
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
503
            self.create_repository()
 
504
            self.create_branch()
 
505
            from bzrlib.workingtree import WorkingTree
 
506
            WorkingTree.create(self.open_branch(), transport.base)
 
507
        else:
 
508
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
509
 
 
510
        # BzrBranch creates a clone to .bzr and then forgets about the
 
511
        # original transport. A ScratchTransport() deletes itself and
 
512
        # everything underneath it when it goes away, so we need to
 
513
        # grab a local copy to prevent that from happening
 
514
        self._transport = transport
 
515
 
 
516
        for d in dirs:
 
517
            self._transport.mkdir(d)
 
518
            
 
519
        for f in files:
 
520
            self._transport.put(f, 'content of %s' % f)
 
521
 
 
522
    def clone(self):
 
523
        """
 
524
        >>> orig = ScratchDir(files=["file1", "file2"])
 
525
        >>> os.listdir(orig.base)
 
526
        [u'.bzr', u'file1', u'file2']
 
527
        >>> clone = orig.clone()
 
528
        >>> if os.name != 'nt':
 
529
        ...   os.path.samefile(orig.base, clone.base)
 
530
        ... else:
 
531
        ...   orig.base == clone.base
 
532
        ...
 
533
        False
 
534
        >>> os.listdir(clone.base)
 
535
        [u'.bzr', u'file1', u'file2']
 
536
        """
 
537
        from shutil import copytree
 
538
        from bzrlib.osutils import mkdtemp
 
539
        base = mkdtemp()
 
540
        os.rmdir(base)
 
541
        copytree(self.base, base, symlinks=True)
 
542
        return ScratchDir(
 
543
            transport=bzrlib.transport.local.ScratchTransport(base))