1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
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.
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.
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
17
from cStringIO import StringIO
19
from bzrlib.lazy_import import lazy_import
20
lazy_import(globals(), """
33
from bzrlib.decorators import (
37
from bzrlib.symbol_versioning import (
43
# XXX: The tracking here of lock counts and whether the lock is held is
44
# somewhat redundant with what's done in LockDir; the main difference is that
45
# LockableFiles permits reentrancy.
47
class _LockWarner(object):
48
"""Hold a counter for a lock and warn if GCed while the count is >= 1.
50
This is separate from LockableFiles because putting a __del__ on
51
LockableFiles can result in uncollectable cycles.
54
def __init__(self, repr):
59
if self.lock_count >= 1:
60
# There should have been a try/finally to unlock this.
61
warnings.warn("%r was gc'd while locked" % self.repr)
64
class LockableFiles(object):
65
"""Object representing a set of related files locked within the same scope.
67
These files are used by a WorkingTree, Repository or Branch, and should
68
generally only be touched by that object.
70
LockableFiles also provides some policy on top of Transport for encoding
71
control files as utf-8.
73
LockableFiles manage a lock count and can be locked repeatedly by
74
a single caller. (The underlying lock implementation generally does not
77
Instances of this class are often called control_files.
79
This object builds on top of a Transport, which is used to actually write
80
the files to disk, and an OSLock or LockDir, which controls how access to
81
the files is controlled. The particular type of locking used is set when
82
the object is constructed. In older formats OSLocks are used everywhere.
83
in newer formats a LockDir is used for Repositories and Branches, and
84
OSLocks for the local filesystem.
86
This class is now deprecated; code should move to using the Transport
87
directly for file operations and using the lock or CountedLock for
90
:ivar _lock: The real underlying lock (e.g. a LockDir)
91
:ivar _counted_lock: A lock decorated with a semaphore, so that it
95
# _lock_mode: None, or 'r' or 'w'
97
# _lock_count: If _lock_mode is true, a positive count of the number of
98
# times the lock has been taken *by this process*.
100
def __init__(self, transport, lock_name, lock_class):
101
"""Create a LockableFiles group
103
:param transport: Transport pointing to the directory holding the
104
control files and lock.
105
:param lock_name: Name of the lock guarding these files.
106
:param lock_class: Class of lock strategy to use: typically
107
either LockDir or TransportLock.
109
self._transport = transport
110
self.lock_name = lock_name
111
self._transaction = None
112
self._lock_mode = None
113
self._lock_warner = _LockWarner(repr(self))
115
esc_name = self._escape(lock_name)
116
self._lock = lock_class(transport, esc_name,
117
file_modebits=self._file_mode,
118
dir_modebits=self._dir_mode)
119
self._counted_lock = counted_lock.CountedLock(self._lock)
121
def create_lock(self):
124
This should normally be called only when the LockableFiles directory
125
is first created on disk.
127
self._lock.create(mode=self._dir_mode)
130
return '%s(%r)' % (self.__class__.__name__,
133
return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
135
def break_lock(self):
136
"""Break the lock of this lockable files group if it is held.
138
The current ui factory will be used to prompt for user conformation.
140
self._lock.break_lock()
142
def _escape(self, file_or_path):
143
"""DEPRECATED: Do not use outside this class"""
144
if not isinstance(file_or_path, basestring):
145
file_or_path = '/'.join(file_or_path)
146
if file_or_path == '':
148
return urlutils.escape(osutils.safe_unicode(file_or_path))
150
def _find_modes(self):
151
"""Determine the appropriate modes for files and directories.
153
:deprecated: Replaced by BzrDir._find_modes.
155
# XXX: The properties created by this can be removed or deprecated
156
# once all the _get_text_store methods etc no longer use them.
159
st = self._transport.stat('.')
160
except errors.TransportNotPossible:
161
self._dir_mode = 0755
162
self._file_mode = 0644
164
# Check the directory mode, but also make sure the created
165
# directories and files are read-write for this user. This is
166
# mostly a workaround for filesystems which lie about being able to
167
# write to a directory (cygwin & win32)
168
self._dir_mode = (st.st_mode & 07777) | 00700
169
# Remove the sticky and execute bits for files
170
self._file_mode = self._dir_mode & ~07111
172
@deprecated_method(deprecated_in((1, 6, 0)))
173
def controlfilename(self, file_or_path):
174
"""Return location relative to branch.
176
:deprecated: Use Transport methods instead.
178
return self._transport.abspath(self._escape(file_or_path))
181
@deprecated_method(deprecated_in((1, 5, 0)))
182
def get(self, relpath):
183
"""Get a file as a bytestream.
185
:deprecated: Use a Transport instead of LockableFiles.
187
relpath = self._escape(relpath)
188
return self._transport.get(relpath)
191
@deprecated_method(deprecated_in((1, 5, 0)))
192
def get_utf8(self, relpath):
193
"""Get a file as a unicode stream.
195
:deprecated: Use a Transport instead of LockableFiles.
197
relpath = self._escape(relpath)
198
# DO NOT introduce an errors=replace here.
199
return codecs.getreader('utf-8')(self._transport.get(relpath))
202
@deprecated_method(deprecated_in((1, 6, 0)))
203
def put(self, path, file):
206
:param path: The path to put the file, relative to the .bzr control
208
:param file: A file-like or string object whose contents should be copied.
210
:deprecated: Use Transport methods instead.
212
self._transport.put_file(self._escape(path), file, mode=self._file_mode)
215
@deprecated_method(deprecated_in((1, 6, 0)))
216
def put_bytes(self, path, a_string):
217
"""Write a string of bytes.
219
:param path: The path to put the bytes, relative to the transport root.
220
:param a_string: A string object, whose exact bytes are to be copied.
222
:deprecated: Use Transport methods instead.
224
self._transport.put_bytes(self._escape(path), a_string,
225
mode=self._file_mode)
228
@deprecated_method(deprecated_in((1, 6, 0)))
229
def put_utf8(self, path, a_string):
230
"""Write a string, encoding as utf-8.
232
:param path: The path to put the string, relative to the transport root.
233
:param string: A string or unicode object whose contents should be copied.
235
:deprecated: Use Transport methods instead.
237
# IterableFile would not be needed if Transport.put took iterables
238
# instead of files. ADHB 2005-12-25
239
# RBC 20060103 surely its not needed anyway, with codecs transcode
241
# JAM 20060103 We definitely don't want encode(..., 'replace')
242
# these are valuable files which should have exact contents.
243
if not isinstance(a_string, basestring):
244
raise errors.BzrBadParameterNotString(a_string)
245
self.put_bytes(path, a_string.encode('utf-8'))
247
def leave_in_place(self):
248
"""Set this LockableFiles to not clear the physical lock on unlock."""
249
self._lock.leave_in_place()
251
def dont_leave_in_place(self):
252
"""Set this LockableFiles to clear the physical lock on unlock."""
253
self._lock.dont_leave_in_place()
255
def lock_write(self, token=None):
256
"""Lock this group of files for writing.
258
:param token: if this is already locked, then lock_write will fail
259
unless the token matches the existing lock.
260
:returns: a token if this instance supports tokens, otherwise None.
261
:raises TokenLockingNotSupported: when a token is given but this
262
instance doesn't support using token locks.
263
:raises MismatchedToken: if the specified token doesn't match the token
264
of the existing lock.
266
A token should be passed in if you know that you have locked the object
267
some other way, and need to synchronise this object's state with that
270
# TODO: Upgrade locking to support using a Transport,
271
# and potentially a remote locking protocol
273
if self._lock_mode != 'w' or not self.get_transaction().writeable():
274
raise errors.ReadOnlyError(self)
275
self._lock.validate_token(token)
276
self._lock_warner.lock_count += 1
277
return self._token_from_lock
279
token_from_lock = self._lock.lock_write(token=token)
280
#traceback.print_stack()
281
self._lock_mode = 'w'
282
self._lock_warner.lock_count = 1
283
self._set_write_transaction()
284
self._token_from_lock = token_from_lock
285
return token_from_lock
289
if self._lock_mode not in ('r', 'w'):
290
raise ValueError("invalid lock mode %r" % (self._lock_mode,))
291
self._lock_warner.lock_count += 1
293
self._lock.lock_read()
294
#traceback.print_stack()
295
self._lock_mode = 'r'
296
self._lock_warner.lock_count = 1
297
self._set_read_transaction()
299
def _set_read_transaction(self):
300
"""Setup a read transaction."""
301
self._set_transaction(transactions.ReadOnlyTransaction())
302
# 5K may be excessive, but hey, its a knob.
303
self.get_transaction().set_cache_size(5000)
305
def _set_write_transaction(self):
306
"""Setup a write transaction."""
307
self._set_transaction(transactions.WriteTransaction())
310
if not self._lock_mode:
311
raise errors.LockNotHeld(self)
312
if self._lock_warner.lock_count > 1:
313
self._lock_warner.lock_count -= 1
315
#traceback.print_stack()
316
self._finish_transaction()
320
self._lock_mode = self._lock_warner.lock_count = None
323
def _lock_count(self):
324
return self._lock_warner.lock_count
327
"""Return true if this LockableFiles group is locked"""
328
return self._lock_warner.lock_count >= 1
330
def get_physical_lock_status(self):
331
"""Return physical lock status.
333
Returns true if a lock is held on the transport. If no lock is held, or
334
the underlying locking mechanism does not support querying lock
335
status, false is returned.
338
return self._lock.peek() is not None
339
except NotImplementedError:
342
def get_transaction(self):
343
"""Return the current active transaction.
345
If no transaction is active, this returns a passthrough object
346
for which all data is immediately flushed and no caching happens.
348
if self._transaction is None:
349
return transactions.PassThroughTransaction()
351
return self._transaction
353
def _set_transaction(self, new_transaction):
354
"""Set a new active transaction."""
355
if self._transaction is not None:
356
raise errors.LockError('Branch %s is in a transaction already.' %
358
self._transaction = new_transaction
360
def _finish_transaction(self):
361
"""Exit the current transaction."""
362
if self._transaction is None:
363
raise errors.LockError('Branch %s is not in a transaction' %
365
transaction = self._transaction
366
self._transaction = None
370
class TransportLock(object):
371
"""Locking method which uses transport-dependent locks.
373
On the local filesystem these transform into OS-managed locks.
375
These do not guard against concurrent access via different
378
This is suitable for use only in WorkingTrees (which are at present
381
def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
382
self._transport = transport
383
self._escaped_name = escaped_name
384
self._file_modebits = file_modebits
385
self._dir_modebits = dir_modebits
387
def break_lock(self):
388
raise NotImplementedError(self.break_lock)
390
def leave_in_place(self):
391
raise NotImplementedError(self.leave_in_place)
393
def dont_leave_in_place(self):
394
raise NotImplementedError(self.dont_leave_in_place)
396
def lock_write(self, token=None):
397
if token is not None:
398
raise errors.TokenLockingNotSupported(self)
399
self._lock = self._transport.lock_write(self._escaped_name)
402
self._lock = self._transport.lock_read(self._escaped_name)
409
raise NotImplementedError()
411
def create(self, mode=None):
412
"""Create lock mechanism"""
413
# for old-style locks, create the file now
414
self._transport.put_bytes(self._escaped_name, '',
415
mode=self._file_modebits)
417
def validate_token(self, token):
418
if token is not None:
419
raise errors.TokenLockingNotSupported(self)