/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/lockable_files.py

  • Committer: Martin Pool
  • Date: 2009-08-04 11:25:23 UTC
  • mto: This revision was merged to the branch mainline in revision 4585.
  • Revision ID: mbp@sourcefrog.net-20090804112523-hijycs32uketxw52
Only give the warning about hardlinking if it was actually requested!

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2008, 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from cStringIO import StringIO
 
18
 
 
19
from bzrlib.lazy_import import lazy_import
 
20
lazy_import(globals(), """
 
21
import codecs
 
22
import warnings
 
23
 
 
24
from bzrlib import (
 
25
    counted_lock,
 
26
    errors,
 
27
    lock,
 
28
    osutils,
 
29
    transactions,
 
30
    urlutils,
 
31
    )
 
32
""")
 
33
 
 
34
from bzrlib.decorators import (
 
35
    needs_read_lock,
 
36
    needs_write_lock,
 
37
    )
 
38
from bzrlib.symbol_versioning import (
 
39
    deprecated_in,
 
40
    deprecated_method,
 
41
    )
 
42
 
 
43
 
 
44
# XXX: The tracking here of lock counts and whether the lock is held is
 
45
# somewhat redundant with what's done in LockDir; the main difference is that
 
46
# LockableFiles permits reentrancy.
 
47
 
 
48
class _LockWarner(object):
 
49
    """Hold a counter for a lock and warn if GCed while the count is >= 1.
 
50
 
 
51
    This is separate from LockableFiles because putting a __del__ on
 
52
    LockableFiles can result in uncollectable cycles.
 
53
    """
 
54
 
 
55
    def __init__(self, repr):
 
56
        self.lock_count = 0
 
57
        self.repr = repr
 
58
 
 
59
    def __del__(self):
 
60
        if self.lock_count >= 1:
 
61
            # There should have been a try/finally to unlock this.
 
62
            warnings.warn("%r was gc'd while locked" % self.repr)
 
63
 
 
64
 
 
65
class LockableFiles(object):
 
66
    """Object representing a set of related files locked within the same scope.
 
67
 
 
68
    These files are used by a WorkingTree, Repository or Branch, and should
 
69
    generally only be touched by that object.
 
70
 
 
71
    LockableFiles also provides some policy on top of Transport for encoding
 
72
    control files as utf-8.
 
73
 
 
74
    LockableFiles manage a lock count and can be locked repeatedly by
 
75
    a single caller.  (The underlying lock implementation generally does not
 
76
    support this.)
 
77
 
 
78
    Instances of this class are often called control_files.
 
79
 
 
80
    This object builds on top of a Transport, which is used to actually write
 
81
    the files to disk, and an OSLock or LockDir, which controls how access to
 
82
    the files is controlled.  The particular type of locking used is set when
 
83
    the object is constructed.  In older formats OSLocks are used everywhere.
 
84
    in newer formats a LockDir is used for Repositories and Branches, and
 
85
    OSLocks for the local filesystem.
 
86
 
 
87
    This class is now deprecated; code should move to using the Transport
 
88
    directly for file operations and using the lock or CountedLock for
 
89
    locking.
 
90
    
 
91
    :ivar _lock: The real underlying lock (e.g. a LockDir)
 
92
    :ivar _counted_lock: A lock decorated with a semaphore, so that it 
 
93
        can be re-entered.
 
94
    """
 
95
 
 
96
    # _lock_mode: None, or 'r' or 'w'
 
97
 
 
98
    # _lock_count: If _lock_mode is true, a positive count of the number of
 
99
    # times the lock has been taken *by this process*.
 
100
 
 
101
    def __init__(self, transport, lock_name, lock_class):
 
102
        """Create a LockableFiles group
 
103
 
 
104
        :param transport: Transport pointing to the directory holding the
 
105
            control files and lock.
 
106
        :param lock_name: Name of the lock guarding these files.
 
107
        :param lock_class: Class of lock strategy to use: typically
 
108
            either LockDir or TransportLock.
 
109
        """
 
110
        self._transport = transport
 
111
        self.lock_name = lock_name
 
112
        self._transaction = None
 
113
        self._lock_mode = None
 
114
        self._lock_warner = _LockWarner(repr(self))
 
115
        self._find_modes()
 
116
        esc_name = self._escape(lock_name)
 
117
        self._lock = lock_class(transport, esc_name,
 
118
                                file_modebits=self._file_mode,
 
119
                                dir_modebits=self._dir_mode)
 
120
        self._counted_lock = counted_lock.CountedLock(self._lock)
 
121
 
 
122
    def create_lock(self):
 
123
        """Create the lock.
 
124
 
 
125
        This should normally be called only when the LockableFiles directory
 
126
        is first created on disk.
 
127
        """
 
128
        self._lock.create(mode=self._dir_mode)
 
129
 
 
130
    def __repr__(self):
 
131
        return '%s(%r)' % (self.__class__.__name__,
 
132
                           self._transport)
 
133
    def __str__(self):
 
134
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
 
135
 
 
136
    def break_lock(self):
 
137
        """Break the lock of this lockable files group if it is held.
 
138
 
 
139
        The current ui factory will be used to prompt for user conformation.
 
140
        """
 
141
        self._lock.break_lock()
 
142
 
 
143
    def _escape(self, file_or_path):
 
144
        """DEPRECATED: Do not use outside this class"""
 
145
        if not isinstance(file_or_path, basestring):
 
146
            file_or_path = '/'.join(file_or_path)
 
147
        if file_or_path == '':
 
148
            return u''
 
149
        return urlutils.escape(osutils.safe_unicode(file_or_path))
 
150
 
 
151
    def _find_modes(self):
 
152
        """Determine the appropriate modes for files and directories.
 
153
 
 
154
        :deprecated: Replaced by BzrDir._find_modes.
 
155
        """
 
156
        # XXX: The properties created by this can be removed or deprecated
 
157
        # once all the _get_text_store methods etc no longer use them.
 
158
        # -- mbp 20080512
 
159
        try:
 
160
            st = self._transport.stat('.')
 
161
        except errors.TransportNotPossible:
 
162
            self._dir_mode = 0755
 
163
            self._file_mode = 0644
 
164
        else:
 
165
            # Check the directory mode, but also make sure the created
 
166
            # directories and files are read-write for this user. This is
 
167
            # mostly a workaround for filesystems which lie about being able to
 
168
            # write to a directory (cygwin & win32)
 
169
            self._dir_mode = (st.st_mode & 07777) | 00700
 
170
            # Remove the sticky and execute bits for files
 
171
            self._file_mode = self._dir_mode & ~07111
 
172
 
 
173
    @deprecated_method(deprecated_in((1, 6, 0)))
 
174
    def controlfilename(self, file_or_path):
 
175
        """Return location relative to branch.
 
176
 
 
177
        :deprecated: Use Transport methods instead.
 
178
        """
 
179
        return self._transport.abspath(self._escape(file_or_path))
 
180
 
 
181
    @needs_read_lock
 
182
    @deprecated_method(deprecated_in((1, 5, 0)))
 
183
    def get(self, relpath):
 
184
        """Get a file as a bytestream.
 
185
 
 
186
        :deprecated: Use a Transport instead of LockableFiles.
 
187
        """
 
188
        relpath = self._escape(relpath)
 
189
        return self._transport.get(relpath)
 
190
 
 
191
    @needs_read_lock
 
192
    @deprecated_method(deprecated_in((1, 5, 0)))
 
193
    def get_utf8(self, relpath):
 
194
        """Get a file as a unicode stream.
 
195
 
 
196
        :deprecated: Use a Transport instead of LockableFiles.
 
197
        """
 
198
        relpath = self._escape(relpath)
 
199
        # DO NOT introduce an errors=replace here.
 
200
        return codecs.getreader('utf-8')(self._transport.get(relpath))
 
201
 
 
202
    @needs_write_lock
 
203
    @deprecated_method(deprecated_in((1, 6, 0)))
 
204
    def put(self, path, file):
 
205
        """Write a file.
 
206
 
 
207
        :param path: The path to put the file, relative to the .bzr control
 
208
                     directory
 
209
        :param file: A file-like or string object whose contents should be copied.
 
210
 
 
211
        :deprecated: Use Transport methods instead.
 
212
        """
 
213
        self._transport.put_file(self._escape(path), file, mode=self._file_mode)
 
214
 
 
215
    @needs_write_lock
 
216
    @deprecated_method(deprecated_in((1, 6, 0)))
 
217
    def put_bytes(self, path, a_string):
 
218
        """Write a string of bytes.
 
219
 
 
220
        :param path: The path to put the bytes, relative to the transport root.
 
221
        :param a_string: A string object, whose exact bytes are to be copied.
 
222
 
 
223
        :deprecated: Use Transport methods instead.
 
224
        """
 
225
        self._transport.put_bytes(self._escape(path), a_string,
 
226
                                  mode=self._file_mode)
 
227
 
 
228
    @needs_write_lock
 
229
    @deprecated_method(deprecated_in((1, 6, 0)))
 
230
    def put_utf8(self, path, a_string):
 
231
        """Write a string, encoding as utf-8.
 
232
 
 
233
        :param path: The path to put the string, relative to the transport root.
 
234
        :param string: A string or unicode object whose contents should be copied.
 
235
 
 
236
        :deprecated: Use Transport methods instead.
 
237
        """
 
238
        # IterableFile would not be needed if Transport.put took iterables
 
239
        # instead of files.  ADHB 2005-12-25
 
240
        # RBC 20060103 surely its not needed anyway, with codecs transcode
 
241
        # file support ?
 
242
        # JAM 20060103 We definitely don't want encode(..., 'replace')
 
243
        # these are valuable files which should have exact contents.
 
244
        if not isinstance(a_string, basestring):
 
245
            raise errors.BzrBadParameterNotString(a_string)
 
246
        self.put_bytes(path, a_string.encode('utf-8'))
 
247
 
 
248
    def leave_in_place(self):
 
249
        """Set this LockableFiles to not clear the physical lock on unlock."""
 
250
        self._lock.leave_in_place()
 
251
 
 
252
    def dont_leave_in_place(self):
 
253
        """Set this LockableFiles to clear the physical lock on unlock."""
 
254
        self._lock.dont_leave_in_place()
 
255
 
 
256
    def lock_write(self, token=None):
 
257
        """Lock this group of files for writing.
 
258
 
 
259
        :param token: if this is already locked, then lock_write will fail
 
260
            unless the token matches the existing lock.
 
261
        :returns: a token if this instance supports tokens, otherwise None.
 
262
        :raises TokenLockingNotSupported: when a token is given but this
 
263
            instance doesn't support using token locks.
 
264
        :raises MismatchedToken: if the specified token doesn't match the token
 
265
            of the existing lock.
 
266
 
 
267
        A token should be passed in if you know that you have locked the object
 
268
        some other way, and need to synchronise this object's state with that
 
269
        fact.
 
270
        """
 
271
        # TODO: Upgrade locking to support using a Transport,
 
272
        # and potentially a remote locking protocol
 
273
        if self._lock_mode:
 
274
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
 
275
                raise errors.ReadOnlyError(self)
 
276
            self._lock.validate_token(token)
 
277
            self._lock_warner.lock_count += 1
 
278
            return self._token_from_lock
 
279
        else:
 
280
            token_from_lock = self._lock.lock_write(token=token)
 
281
            #traceback.print_stack()
 
282
            self._lock_mode = 'w'
 
283
            self._lock_warner.lock_count = 1
 
284
            self._set_write_transaction()
 
285
            self._token_from_lock = token_from_lock
 
286
            return token_from_lock
 
287
 
 
288
    def lock_read(self):
 
289
        if self._lock_mode:
 
290
            if self._lock_mode not in ('r', 'w'):
 
291
                raise ValueError("invalid lock mode %r" % (self._lock_mode,))
 
292
            self._lock_warner.lock_count += 1
 
293
        else:
 
294
            self._lock.lock_read()
 
295
            #traceback.print_stack()
 
296
            self._lock_mode = 'r'
 
297
            self._lock_warner.lock_count = 1
 
298
            self._set_read_transaction()
 
299
 
 
300
    def _set_read_transaction(self):
 
301
        """Setup a read transaction."""
 
302
        self._set_transaction(transactions.ReadOnlyTransaction())
 
303
        # 5K may be excessive, but hey, its a knob.
 
304
        self.get_transaction().set_cache_size(5000)
 
305
 
 
306
    def _set_write_transaction(self):
 
307
        """Setup a write transaction."""
 
308
        self._set_transaction(transactions.WriteTransaction())
 
309
 
 
310
    def unlock(self):
 
311
        if not self._lock_mode:
 
312
            return lock.cant_unlock_not_held(self)
 
313
        if self._lock_warner.lock_count > 1:
 
314
            self._lock_warner.lock_count -= 1
 
315
        else:
 
316
            #traceback.print_stack()
 
317
            self._finish_transaction()
 
318
            try:
 
319
                self._lock.unlock()
 
320
            finally:
 
321
                self._lock_mode = self._lock_warner.lock_count = None
 
322
 
 
323
    @property
 
324
    def _lock_count(self):
 
325
        return self._lock_warner.lock_count
 
326
 
 
327
    def is_locked(self):
 
328
        """Return true if this LockableFiles group is locked"""
 
329
        return self._lock_warner.lock_count >= 1
 
330
 
 
331
    def get_physical_lock_status(self):
 
332
        """Return physical lock status.
 
333
 
 
334
        Returns true if a lock is held on the transport. If no lock is held, or
 
335
        the underlying locking mechanism does not support querying lock
 
336
        status, false is returned.
 
337
        """
 
338
        try:
 
339
            return self._lock.peek() is not None
 
340
        except NotImplementedError:
 
341
            return False
 
342
 
 
343
    def get_transaction(self):
 
344
        """Return the current active transaction.
 
345
 
 
346
        If no transaction is active, this returns a passthrough object
 
347
        for which all data is immediately flushed and no caching happens.
 
348
        """
 
349
        if self._transaction is None:
 
350
            return transactions.PassThroughTransaction()
 
351
        else:
 
352
            return self._transaction
 
353
 
 
354
    def _set_transaction(self, new_transaction):
 
355
        """Set a new active transaction."""
 
356
        if self._transaction is not None:
 
357
            raise errors.LockError('Branch %s is in a transaction already.' %
 
358
                                   self)
 
359
        self._transaction = new_transaction
 
360
 
 
361
    def _finish_transaction(self):
 
362
        """Exit the current transaction."""
 
363
        if self._transaction is None:
 
364
            raise errors.LockError('Branch %s is not in a transaction' %
 
365
                                   self)
 
366
        transaction = self._transaction
 
367
        self._transaction = None
 
368
        transaction.finish()
 
369
 
 
370
 
 
371
class TransportLock(object):
 
372
    """Locking method which uses transport-dependent locks.
 
373
 
 
374
    On the local filesystem these transform into OS-managed locks.
 
375
 
 
376
    These do not guard against concurrent access via different
 
377
    transports.
 
378
 
 
379
    This is suitable for use only in WorkingTrees (which are at present
 
380
    always local).
 
381
    """
 
382
    def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
 
383
        self._transport = transport
 
384
        self._escaped_name = escaped_name
 
385
        self._file_modebits = file_modebits
 
386
        self._dir_modebits = dir_modebits
 
387
 
 
388
    def break_lock(self):
 
389
        raise NotImplementedError(self.break_lock)
 
390
 
 
391
    def leave_in_place(self):
 
392
        raise NotImplementedError(self.leave_in_place)
 
393
 
 
394
    def dont_leave_in_place(self):
 
395
        raise NotImplementedError(self.dont_leave_in_place)
 
396
 
 
397
    def lock_write(self, token=None):
 
398
        if token is not None:
 
399
            raise errors.TokenLockingNotSupported(self)
 
400
        self._lock = self._transport.lock_write(self._escaped_name)
 
401
 
 
402
    def lock_read(self):
 
403
        self._lock = self._transport.lock_read(self._escaped_name)
 
404
 
 
405
    def unlock(self):
 
406
        self._lock.unlock()
 
407
        self._lock = None
 
408
 
 
409
    def peek(self):
 
410
        raise NotImplementedError()
 
411
 
 
412
    def create(self, mode=None):
 
413
        """Create lock mechanism"""
 
414
        # for old-style locks, create the file now
 
415
        self._transport.put_bytes(self._escaped_name, '',
 
416
                            mode=self._file_modebits)
 
417
 
 
418
    def validate_token(self, token):
 
419
        if token is not None:
 
420
            raise errors.TokenLockingNotSupported(self)
 
421