2
import bzrlib.errors as errors
3
from bzrlib.errors import LockError, ReadOnlyError
4
from bzrlib.trace import mutter
5
import bzrlib.transactions as transactions
7
class LockableFiles(object):
8
"""Object representing a set of lockable files
14
If _lock_mode is true, a positive count of the number of times the
18
Lock object from bzrlib.lock.
25
def __init__(self, transport, lock_name):
27
self._transport = transport
28
self.lock_name = lock_name
29
self._transaction = None
32
if self._lock_mode or self._lock:
33
# XXX: This should show something every time, and be suitable for
34
# headless operation and embedding
35
warn("file group %r was not explicitly unlocked" % self)
38
def _rel_controlfilename(self, file_or_path):
39
if not isinstance(file_or_path, basestring):
40
file_or_path = '/'.join(file_or_path)
41
if file_or_path == '':
42
return unicode(bzrlib.BZRDIR)
43
return bzrlib.transport.urlescape(unicode(bzrlib.BZRDIR + '/' + file_or_path))
45
def controlfilename(self, file_or_path):
46
"""Return location relative to branch."""
47
return self._transport.abspath(self._rel_controlfilename(file_or_path))
49
def controlfile(self, file_or_path, mode='r'):
50
"""Open a control file for this branch.
52
There are two classes of file in the control directory: text
53
and binary. binary files are untranslated byte streams. Text
54
control files are stored with Unix newlines and in UTF-8, even
55
if the platform or locale defaults are different.
57
Controlfiles should almost never be opened in write mode but
58
rather should be atomically copied and replaced using atomicfile.
62
relpath = self._rel_controlfilename(file_or_path)
63
#TODO: codecs.open() buffers linewise, so it was overloaded with
64
# a much larger buffer, do we need to do the same for getreader/getwriter?
66
return self._transport.get(relpath)
68
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
70
# XXX: Do we really want errors='replace'? Perhaps it should be
71
# an error, or at least reported, if there's incorrectly-encoded
73
# <https://launchpad.net/products/bzr/+bug/3823>
74
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
76
raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
78
raise BzrError("invalid controlfile mode %r" % mode)
80
def put(self, path, file):
83
:param path: The path to put the file, relative to the .bzr control
85
:param f: A file-like or string object whose contents should be copied.
87
if not self._lock_mode == 'w':
89
self._transport.put(self._rel_controlfilename(path), file)
91
def put_utf8(self, path, file):
92
"""Write a file, encoding as utf-8.
94
:param path: The path to put the file, relative to the .bzr control
96
:param f: A file-like or string object whose contents should be copied.
100
if isinstance(file, basestring):
101
file = file.encode('utf-8', 'replace')
103
file = codecs.getwriter('utf-8')(file, errors='replace')
106
def lock_write(self):
107
mutter("lock write: %s (%s)", self, self._lock_count)
108
# TODO: Upgrade locking to support using a Transport,
109
# and potentially a remote locking protocol
111
if self._lock_mode != 'w':
112
raise LockError("can't upgrade to a write lock from %r" %
114
self._lock_count += 1
116
self._lock = self._transport.lock_write(
117
self._rel_controlfilename(self.lock_name))
118
self._lock_mode = 'w'
120
self._set_transaction(transactions.PassThroughTransaction())
123
mutter("lock read: %s (%s)", self, self._lock_count)
125
assert self._lock_mode in ('r', 'w'), \
126
"invalid lock mode %r" % self._lock_mode
127
self._lock_count += 1
129
self._lock = self._transport.lock_read(
130
self._rel_controlfilename('branch-lock'))
131
self._lock_mode = 'r'
133
self._set_transaction(transactions.ReadOnlyTransaction())
134
# 5K may be excessive, but hey, its a knob.
135
self.get_transaction().set_cache_size(5000)
138
mutter("unlock: %s (%s)", self, self._lock_count)
139
if not self._lock_mode:
140
raise LockError('branch %r is not locked' % (self))
142
if self._lock_count > 1:
143
self._lock_count -= 1
145
self._finish_transaction()
148
self._lock_mode = self._lock_count = None
150
def make_transport(self, relpath):
151
return self._transport.clone(relpath)
153
def get_transaction(self):
154
"""Return the current active transaction.
156
If no transaction is active, this returns a passthrough object
157
for which all data is immediately flushed and no caching happens.
159
if self._transaction is None:
160
return transactions.PassThroughTransaction()
162
return self._transaction
164
def _set_transaction(self, new_transaction):
165
"""Set a new active transaction."""
166
if self._transaction is not None:
167
raise errors.LockError('Branch %s is in a transaction already.' %
169
self._transaction = new_transaction
171
def _finish_transaction(self):
172
"""Exit the current transaction."""
173
if self._transaction is None:
174
raise errors.LockError('Branch %s is not in a transaction' %
176
transaction = self._transaction
177
self._transaction = None